1 /* 2 * Copyright (C) 2024 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.window; 18 19 import static android.content.res.Resources.ID_NULL; 20 import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; 21 import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT; 22 import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER; 23 import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES; 24 25 import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; 26 27 import static org.junit.Assert.assertEquals; 28 import static org.junit.Assert.assertNotNull; 29 import static org.junit.Assert.assertTrue; 30 import static org.junit.Assert.fail; 31 32 import android.content.Context; 33 import android.graphics.Color; 34 import android.graphics.Insets; 35 import android.graphics.Rect; 36 import android.os.Bundle; 37 import android.platform.test.annotations.Presubmit; 38 import android.platform.test.annotations.RequiresFlagsEnabled; 39 import android.server.wm.WindowManagerTestBase; 40 import android.server.wm.cts.R; 41 import android.util.DisplayMetrics; 42 import android.view.Display; 43 import android.view.View; 44 import android.view.Window; 45 import android.view.WindowInsets; 46 47 import com.android.window.flags.Flags; 48 49 import org.junit.Test; 50 51 import java.util.concurrent.CountDownLatch; 52 import java.util.concurrent.TimeUnit; 53 54 /** 55 * Ensure window policies are applied as expected. 56 * 57 * Build/Install/Run: 58 * atest CtsWindowManagerDeviceWindow:WindowPolicyTests 59 */ 60 @Presubmit 61 public class WindowPolicyTests extends WindowManagerTestBase { 62 63 @Override setUp()64 public void setUp() throws Exception { 65 super.setUp(); 66 TestActivity.sStyleId = ID_NULL; 67 TestActivity.sLayoutInDisplayCutoutMode = 68 TestActivity.LAYOUT_IN_DISPLAY_CUTOUT_MODE_UNSPECIFIED; 69 } 70 71 @RequiresFlagsEnabled(Flags.FLAG_ENFORCE_EDGE_TO_EDGE) 72 @Test testWindowInsets()73 public void testWindowInsets() { 74 final TestActivity activity = startActivitySync(TestActivity.class); 75 76 getInstrumentation().runOnMainSync(() -> { 77 assertEquals( 78 "WindowInsets must not be changed.", 79 activity.mContentView.getRootWindowInsets(), 80 activity.mContentView.mWindowInsets); 81 }); 82 } 83 assertFillWindowBounds(TestActivity activity)84 private static void assertFillWindowBounds(TestActivity activity) { 85 getInstrumentation().runOnMainSync(() -> { 86 assertEquals( 87 "Decor view must fill window bounds.", 88 activity.getResources().getConfiguration().windowConfiguration.getBounds(), 89 getFrameOnScreen(activity.getWindow().getDecorView())); 90 assertEquals( 91 "Content view must fill window bounds.", 92 activity.getResources().getConfiguration().windowConfiguration.getBounds(), 93 getFrameOnScreen(activity.mContentView)); 94 }); 95 } 96 getFrameOnScreen(View view)97 private static Rect getFrameOnScreen(View view) { 98 final int[] location = {0, 0}; 99 view.getLocationOnScreen(location); 100 return new Rect( 101 location[0], 102 location[1], 103 location[0] + view.getWidth(), 104 location[1] + view.getHeight()); 105 } 106 107 @RequiresFlagsEnabled(Flags.FLAG_ENFORCE_EDGE_TO_EDGE) 108 @Test testWindowStyleLayoutInDisplayCutoutMode_unspecified()109 public void testWindowStyleLayoutInDisplayCutoutMode_unspecified() { 110 TestActivity.sStyleId = R.style.LayoutInDisplayCutoutModeUnspecified; 111 assertFillWindowBounds(startActivitySync(TestActivity.class)); 112 } 113 114 @RequiresFlagsEnabled(Flags.FLAG_ENFORCE_EDGE_TO_EDGE) 115 @Test testWindowStyleLayoutInDisplayCutoutMode_never()116 public void testWindowStyleLayoutInDisplayCutoutMode_never() { 117 TestActivity.sStyleId = R.style.LayoutInDisplayCutoutModeNever; 118 assertFillWindowBounds(startActivitySync(TestActivity.class)); 119 } 120 121 @RequiresFlagsEnabled(Flags.FLAG_ENFORCE_EDGE_TO_EDGE) 122 @Test testWindowStyleLayoutInDisplayCutoutMode_default()123 public void testWindowStyleLayoutInDisplayCutoutMode_default() { 124 TestActivity.sStyleId = R.style.LayoutInDisplayCutoutModeDefault; 125 assertFillWindowBounds(startActivitySync(TestActivity.class)); 126 } 127 128 @RequiresFlagsEnabled(Flags.FLAG_ENFORCE_EDGE_TO_EDGE) 129 @Test testWindowStyleLayoutInDisplayCutoutMode_shortEdges()130 public void testWindowStyleLayoutInDisplayCutoutMode_shortEdges() { 131 TestActivity.sStyleId = R.style.LayoutInDisplayCutoutModeShortEdges; 132 assertFillWindowBounds(startActivitySync(TestActivity.class)); 133 } 134 135 @RequiresFlagsEnabled(Flags.FLAG_ENFORCE_EDGE_TO_EDGE) 136 @Test testWindowStyleLayoutInDisplayCutoutMode_always()137 public void testWindowStyleLayoutInDisplayCutoutMode_always() { 138 TestActivity.sStyleId = R.style.LayoutInDisplayCutoutModeAlways; 139 assertFillWindowBounds(startActivitySync(TestActivity.class)); 140 } 141 142 @RequiresFlagsEnabled(Flags.FLAG_ENFORCE_EDGE_TO_EDGE) 143 @Test testLayoutParamsLayoutInDisplayCutoutMode_unspecified()144 public void testLayoutParamsLayoutInDisplayCutoutMode_unspecified() { 145 assertFillWindowBounds(startActivitySync(TestActivity.class)); 146 } 147 148 @RequiresFlagsEnabled(Flags.FLAG_ENFORCE_EDGE_TO_EDGE) 149 @Test testLayoutParamsLayoutInDisplayCutoutMode_never()150 public void testLayoutParamsLayoutInDisplayCutoutMode_never() { 151 TestActivity.sLayoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER; 152 assertFillWindowBounds(startActivitySync(TestActivity.class)); 153 } 154 155 @RequiresFlagsEnabled(Flags.FLAG_ENFORCE_EDGE_TO_EDGE) 156 @Test testLayoutParamsLayoutInDisplayCutoutMode_default()157 public void testLayoutParamsLayoutInDisplayCutoutMode_default() { 158 TestActivity.sLayoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT; 159 assertFillWindowBounds(startActivitySync(TestActivity.class)); 160 } 161 162 @RequiresFlagsEnabled(Flags.FLAG_ENFORCE_EDGE_TO_EDGE) 163 @Test testLayoutParamsLayoutInDisplayCutoutMode_shortEdges()164 public void testLayoutParamsLayoutInDisplayCutoutMode_shortEdges() { 165 TestActivity.sLayoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES; 166 assertFillWindowBounds(startActivitySync(TestActivity.class)); 167 } 168 169 @RequiresFlagsEnabled(Flags.FLAG_ENFORCE_EDGE_TO_EDGE) 170 @Test testLayoutParamsLayoutInDisplayCutoutMode_always()171 public void testLayoutParamsLayoutInDisplayCutoutMode_always() { 172 TestActivity.sLayoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; 173 assertFillWindowBounds(startActivitySync(TestActivity.class)); 174 } 175 176 @RequiresFlagsEnabled(Flags.FLAG_INSETS_DECOUPLED_CONFIGURATION) 177 @Test testOptOutEdgeToEdgeAppBounds()178 public void testOptOutEdgeToEdgeAppBounds() { 179 TestActivity.sLayoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; 180 TestActivity activity = startActivitySync(OptOutEdgeToEdgeActivity.class); 181 getInstrumentation().runOnMainSync(() -> { 182 final WindowInsets windowInsets = 183 activity.getWindow().getDecorView().getRootWindowInsets(); 184 final Insets insets = windowInsets.getInsets( 185 WindowInsets.Type.displayCutout() | WindowInsets.Type.navigationBars()); 186 final Rect expectedBounds = new Rect( 187 activity.getResources().getConfiguration().windowConfiguration.getBounds()); 188 expectedBounds.inset(insets); 189 assertEquals( 190 "The bounds must exclude display cutout and navigation bar area.", 191 expectedBounds, 192 activity.getResources().getConfiguration().windowConfiguration.getAppBounds()); 193 }); 194 } 195 196 @RequiresFlagsEnabled(Flags.FLAG_INSETS_DECOUPLED_CONFIGURATION) 197 @Test testOptOutEdgeToEdgeDisplayMetrics()198 public void testOptOutEdgeToEdgeDisplayMetrics() { 199 TestActivity.sLayoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; 200 TestActivity activity = startActivitySync(OptOutEdgeToEdgeActivity.class); 201 getInstrumentation().runOnMainSync(() -> { 202 final WindowInsets windowInsets = 203 activity.getWindow().getDecorView().getRootWindowInsets(); 204 final Insets insets = windowInsets.getInsets( 205 WindowInsets.Type.displayCutout() | WindowInsets.Type.navigationBars()); 206 final Rect expectedBounds = new Rect( 207 activity.getResources().getConfiguration().windowConfiguration.getBounds()); 208 expectedBounds.inset(insets); 209 final Display display = activity.getDisplay(); 210 assertNotNull(display); 211 final DisplayMetrics displayMetrics = new DisplayMetrics(); 212 display.getMetrics(displayMetrics); 213 assertEquals( 214 "The width must exclude display cutout and navigation bar area.", 215 expectedBounds.width(), 216 displayMetrics.widthPixels); 217 assertEquals( 218 "The height must exclude display cutout and navigation bar area.", 219 expectedBounds.height(), 220 displayMetrics.heightPixels); 221 }); 222 } 223 224 @RequiresFlagsEnabled(Flags.FLAG_ENFORCE_EDGE_TO_EDGE) 225 @Test testSystemBarColor()226 public void testSystemBarColor() { 227 TestActivity.sStyleId = R.style.BlackSystemBars; 228 final TestActivity activity = startActivitySync(TestActivity.class); 229 getInstrumentation().runOnMainSync(() -> { 230 final Window window = activity.getWindow(); 231 assertEquals("Status bar color must be transparent.", 232 Color.TRANSPARENT, Color.alpha(window.getStatusBarColor())); 233 assertEquals("Navigation bar color must be transparent.", 234 Color.TRANSPARENT, window.getNavigationBarColor()); 235 assertEquals("Navigation bar divider color must be transparent.", 236 Color.TRANSPARENT, window.getNavigationBarDividerColor()); 237 238 window.setStatusBarColor(Color.BLACK); 239 assertEquals("Status bar color must not be changed.", 240 Color.TRANSPARENT, window.getStatusBarColor()); 241 window.setNavigationBarColor(Color.BLACK); 242 assertEquals("Navigation bar color must not be changed.", 243 Color.TRANSPARENT, window.getNavigationBarColor()); 244 window.setNavigationBarDividerColor(Color.BLACK); 245 assertEquals("Navigation bar divider color not be changed.", 246 Color.TRANSPARENT, window.getNavigationBarDividerColor()); 247 }); 248 } 249 startActivitySync(Class<T> activityClass)250 private static <T extends TestActivity> TestActivity startActivitySync(Class<T> activityClass) { 251 final TestActivity activity = startActivity(activityClass); 252 activity.waitForLayout(); 253 return activity; 254 } 255 256 public static class TestActivity extends FocusableActivity { 257 258 private static final int LAYOUT_IN_DISPLAY_CUTOUT_MODE_UNSPECIFIED = -1; 259 260 private static int sStyleId = ID_NULL; 261 private static int sLayoutInDisplayCutoutMode = -1; 262 private final CountDownLatch mLayoutLatch = new CountDownLatch(1); 263 private ContentView mContentView; 264 265 @Override onCreate(Bundle savedInstanceState)266 protected void onCreate(Bundle savedInstanceState) { 267 super.onCreate(savedInstanceState); 268 if (sStyleId != ID_NULL) { 269 getTheme().applyStyle(sStyleId, true /* force */); 270 } 271 if (sLayoutInDisplayCutoutMode != LAYOUT_IN_DISPLAY_CUTOUT_MODE_UNSPECIFIED) { 272 getWindow().getAttributes().layoutInDisplayCutoutMode = sLayoutInDisplayCutoutMode; 273 } 274 mContentView = new ContentView(this); 275 mContentView.getViewTreeObserver().addOnGlobalLayoutListener(mLayoutLatch::countDown); 276 setContentView(mContentView); 277 } 278 waitForLayout()279 private void waitForLayout() { 280 final String errorMessage = "Unable to wait for layout."; 281 try { 282 assertTrue(errorMessage, mLayoutLatch.await(3, TimeUnit.SECONDS)); 283 } catch (InterruptedException e) { 284 fail(errorMessage); 285 } 286 } 287 288 private static class ContentView extends View { 289 290 private WindowInsets mWindowInsets; 291 ContentView(Context context)292 private ContentView(Context context) { 293 super(context); 294 } 295 296 @Override onApplyWindowInsets(WindowInsets insets)297 public WindowInsets onApplyWindowInsets(WindowInsets insets) { 298 mWindowInsets = insets; 299 return super.onApplyWindowInsets(insets); 300 } 301 } 302 } 303 304 public static class OptOutEdgeToEdgeActivity extends TestActivity { 305 } 306 } 307