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 com.android.server.wm; 18 19 import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; 20 import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; 21 import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; 22 import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; 23 import static android.view.WindowInsets.Type.navigationBars; 24 import static android.view.WindowInsets.Type.statusBars; 25 import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; 26 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION; 27 import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR; 28 import static android.view.WindowManager.LayoutParams.TYPE_NOTIFICATION_SHADE; 29 import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR; 30 import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL; 31 32 import static org.junit.Assert.assertEquals; 33 import static org.junit.Assert.assertFalse; 34 import static org.junit.Assert.assertNotNull; 35 import static org.junit.Assert.assertNull; 36 import static org.junit.Assert.assertTrue; 37 import static org.mockito.Mockito.clearInvocations; 38 import static org.mockito.Mockito.verify; 39 40 import android.app.StatusBarManager; 41 import android.os.Binder; 42 import android.platform.test.annotations.Presubmit; 43 import android.view.InsetsFrameProvider; 44 import android.view.InsetsSource; 45 import android.view.InsetsSourceControl; 46 import android.view.InsetsState; 47 import android.view.WindowInsets; 48 49 import androidx.test.filters.SmallTest; 50 51 import com.android.server.statusbar.StatusBarManagerInternal; 52 53 import org.junit.Before; 54 import org.junit.Test; 55 import org.junit.runner.RunWith; 56 57 @SmallTest 58 @Presubmit 59 @RunWith(WindowTestRunner.class) 60 public class InsetsPolicyTest extends WindowTestsBase { 61 62 @Before setup()63 public void setup() { 64 mWm.mAnimator.ready(); 65 } 66 67 @Test testControlsForDispatch_regular()68 public void testControlsForDispatch_regular() { 69 addStatusBar(); 70 addNavigationBar(); 71 72 final InsetsSourceControl[] controls = addAppWindowAndGetControlsForDispatch(); 73 74 // The app can control both system bars. 75 assertNotNull(controls); 76 assertEquals(2, controls.length); 77 } 78 79 @Test testControlsForDispatch_adjacentTasksVisible()80 public void testControlsForDispatch_adjacentTasksVisible() { 81 addStatusBar(); 82 addNavigationBar(); 83 84 final Task task1 = createTask(mDisplayContent); 85 final Task task2 = createTask(mDisplayContent); 86 task1.setAdjacentTaskFragment(task2); 87 final WindowState win = createAppWindow(task1, WINDOWING_MODE_MULTI_WINDOW, "app"); 88 final InsetsSourceControl[] controls = addWindowAndGetControlsForDispatch(win); 89 90 // The app must not control any system bars. 91 assertNull(controls); 92 } 93 94 @Test testControlsForDispatch_freeformTaskVisible()95 public void testControlsForDispatch_freeformTaskVisible() { 96 addStatusBar(); 97 addNavigationBar(); 98 99 final WindowState win = createWindow(null, WINDOWING_MODE_FREEFORM, 100 ACTIVITY_TYPE_STANDARD, TYPE_APPLICATION, mDisplayContent, "app"); 101 final InsetsSourceControl[] controls = addWindowAndGetControlsForDispatch(win); 102 103 // The app must not control any system bars. 104 assertNull(controls); 105 } 106 107 @Test testControlsForDispatch_forceStatusBarVisible()108 public void testControlsForDispatch_forceStatusBarVisible() { 109 addStatusBar().mAttrs.forciblyShownTypes |= statusBars(); 110 addNavigationBar(); 111 112 final InsetsSourceControl[] controls = addAppWindowAndGetControlsForDispatch(); 113 114 // The focused app window can control both system bars. 115 assertNotNull(controls); 116 assertEquals(2, controls.length); 117 } 118 119 @Test testControlsForDispatch_statusBarForceShowNavigation()120 public void testControlsForDispatch_statusBarForceShowNavigation() { 121 addWindow(TYPE_NOTIFICATION_SHADE, "notificationShade").mAttrs.forciblyShownTypes |= 122 navigationBars(); 123 addStatusBar(); 124 addNavigationBar(); 125 126 final InsetsSourceControl[] controls = addAppWindowAndGetControlsForDispatch(); 127 128 // The focused app window can control both system bars. 129 assertNotNull(controls); 130 assertEquals(2, controls.length); 131 } 132 133 @Test testControlsForDispatch_statusBarForceShowNavigation_butFocusedAnyways()134 public void testControlsForDispatch_statusBarForceShowNavigation_butFocusedAnyways() { 135 WindowState notifShade = addWindow(TYPE_NOTIFICATION_SHADE, "notificationShade"); 136 notifShade.mAttrs.forciblyShownTypes |= navigationBars(); 137 addNavigationBar(); 138 139 mDisplayContent.getInsetsPolicy().updateBarControlTarget(notifShade); 140 InsetsSourceControl[] controls 141 = mDisplayContent.getInsetsStateController().getControlsForDispatch(notifShade); 142 143 // The app controls the navigation bar. 144 assertNotNull(controls); 145 assertEquals(1, controls.length); 146 } 147 148 @Test testControlsForDispatch_remoteInsetsControllerControlsBars_appHasNoControl()149 public void testControlsForDispatch_remoteInsetsControllerControlsBars_appHasNoControl() { 150 mDisplayContent.setRemoteInsetsController(createDisplayWindowInsetsController()); 151 mDisplayContent.getDisplayPolicy().setRemoteInsetsControllerControlsSystemBars(true); 152 addStatusBar(); 153 addNavigationBar(); 154 155 final InsetsSourceControl[] controls = addAppWindowAndGetControlsForDispatch(); 156 157 // The focused app window cannot control system bars. 158 assertNull(controls); 159 } 160 161 @Test testControlsForDispatch_topAppHidesStatusBar()162 public void testControlsForDispatch_topAppHidesStatusBar() { 163 addStatusBar(); 164 addNavigationBar(); 165 166 // Add a fullscreen (MATCH_PARENT x MATCH_PARENT) app window which hides status bar. 167 final WindowState fullscreenApp = addWindow(TYPE_APPLICATION, "fullscreenApp"); 168 fullscreenApp.setRequestedVisibleTypes(0, WindowInsets.Type.statusBars()); 169 170 // Add a non-fullscreen dialog window. 171 final WindowState dialog = addWindow(TYPE_APPLICATION, "dialog"); 172 dialog.mAttrs.width = WRAP_CONTENT; 173 dialog.mAttrs.height = WRAP_CONTENT; 174 175 // Let fullscreenApp be mTopFullscreenOpaqueWindowState. 176 final DisplayPolicy displayPolicy = mDisplayContent.getDisplayPolicy(); 177 displayPolicy.beginPostLayoutPolicyLw(); 178 displayPolicy.applyPostLayoutPolicyLw(dialog, dialog.mAttrs, fullscreenApp, null); 179 displayPolicy.applyPostLayoutPolicyLw(fullscreenApp, fullscreenApp.mAttrs, null, null); 180 displayPolicy.finishPostLayoutPolicyLw(); 181 mDisplayContent.getInsetsPolicy().updateBarControlTarget(dialog); 182 183 assertEquals(fullscreenApp, displayPolicy.getTopFullscreenOpaqueWindow()); 184 185 // dialog is the focused window, but it can only control navigation bar. 186 final InsetsSourceControl[] dialogControls = 187 mDisplayContent.getInsetsStateController().getControlsForDispatch(dialog); 188 assertNotNull(dialogControls); 189 assertEquals(1, dialogControls.length); 190 assertEquals(navigationBars(), dialogControls[0].getType()); 191 192 // fullscreenApp is hiding status bar, and it can keep controlling status bar. 193 final InsetsSourceControl[] fullscreenAppControls = 194 mDisplayContent.getInsetsStateController().getControlsForDispatch(fullscreenApp); 195 assertNotNull(fullscreenAppControls); 196 assertEquals(1, fullscreenAppControls.length); 197 assertEquals(statusBars(), fullscreenAppControls[0].getType()); 198 199 // Assume mFocusedWindow is updated but mTopFullscreenOpaqueWindowState hasn't. 200 final WindowState newFocusedFullscreenApp = addWindow(TYPE_APPLICATION, "newFullscreenApp"); 201 newFocusedFullscreenApp.setRequestedVisibleTypes( 202 WindowInsets.Type.statusBars(), WindowInsets.Type.statusBars()); 203 // Make sure status bar is hidden by previous insets state. 204 mDisplayContent.getInsetsPolicy().updateBarControlTarget(fullscreenApp); 205 206 final StatusBarManagerInternal sbmi = 207 mDisplayContent.getDisplayPolicy().getStatusBarManagerInternal(); 208 clearInvocations(sbmi); 209 mDisplayContent.getInsetsPolicy().updateBarControlTarget(newFocusedFullscreenApp); 210 // The status bar should be shown by newFocusedFullscreenApp even 211 // mTopFullscreenOpaqueWindowState is still fullscreenApp. 212 verify(sbmi).setWindowState(mDisplayContent.mDisplayId, StatusBarManager.WINDOW_STATUS_BAR, 213 StatusBarManager.WINDOW_STATE_SHOWING); 214 215 // Add a system window: panel. 216 final WindowState panel = addWindow(TYPE_STATUS_BAR_SUB_PANEL, "panel"); 217 mDisplayContent.getInsetsPolicy().updateBarControlTarget(panel); 218 219 // panel is the focused window, but it can only control navigation bar. 220 // Because fullscreenApp is hiding status bar. 221 InsetsSourceControl[] panelControls = 222 mDisplayContent.getInsetsStateController().getControlsForDispatch(panel); 223 assertNotNull(panelControls); 224 assertEquals(1, panelControls.length); 225 assertEquals(navigationBars(), panelControls[0].getType()); 226 227 // Add notificationShade and make it can receive keys. 228 final WindowState shade = addWindow(TYPE_NOTIFICATION_SHADE, "notificationShade"); 229 shade.setHasSurface(true); 230 assertTrue(shade.canReceiveKeys()); 231 mDisplayContent.getInsetsPolicy().updateBarControlTarget(panel); 232 233 // panel can control both system bars now. 234 panelControls = mDisplayContent.getInsetsStateController().getControlsForDispatch(panel); 235 assertNotNull(panelControls); 236 assertEquals(2, panelControls.length); 237 238 // Make notificationShade cannot receive keys. 239 shade.mAttrs.flags |= FLAG_NOT_FOCUSABLE; 240 assertFalse(shade.canReceiveKeys()); 241 mDisplayContent.getInsetsPolicy().updateBarControlTarget(panel); 242 243 // panel can only control navigation bar now. 244 panelControls = mDisplayContent.getInsetsStateController().getControlsForDispatch(panel); 245 assertNotNull(panelControls); 246 assertEquals(1, panelControls.length); 247 assertEquals(navigationBars(), panelControls[0].getType()); 248 } 249 250 @SetupWindows(addWindows = W_ACTIVITY) 251 @Test testShowTransientBars_bothCanBeTransient_appGetsBothFakeControls()252 public void testShowTransientBars_bothCanBeTransient_appGetsBothFakeControls() { 253 final WindowState statusBar = addStatusBar(); 254 final InsetsSourceProvider statusBarProvider = statusBar.getControllableInsetProvider(); 255 final int statusBarId = statusBarProvider.getSource().getId(); 256 statusBar.setHasSurface(true); 257 statusBarProvider.setServerVisible(true); 258 final WindowState navBar = addNavigationBar(); 259 final InsetsSourceProvider navBarProvider = statusBar.getControllableInsetProvider(); 260 final int navBarId = statusBarProvider.getSource().getId(); 261 navBar.setHasSurface(true); 262 navBarProvider.setServerVisible(true); 263 final InsetsPolicy policy = mDisplayContent.getInsetsPolicy(); 264 265 // Make both system bars invisible. 266 mAppWindow.setRequestedVisibleTypes( 267 0, navigationBars() | statusBars()); 268 policy.updateBarControlTarget(mAppWindow); 269 waitUntilWindowAnimatorIdle(); 270 assertFalse(mDisplayContent.getInsetsStateController().getRawInsetsState() 271 .isSourceOrDefaultVisible(statusBarId, statusBars())); 272 assertFalse(mDisplayContent.getInsetsStateController().getRawInsetsState() 273 .isSourceOrDefaultVisible(navBarId, navigationBars())); 274 275 policy.showTransient(navigationBars() | statusBars(), true /* isGestureOnSystemBar */); 276 waitUntilWindowAnimatorIdle(); 277 final InsetsSourceControl[] controls = 278 mDisplayContent.getInsetsStateController().getControlsForDispatch(mAppWindow); 279 280 // The app must get both fake controls. 281 assertEquals(2, controls.length); 282 for (int i = controls.length - 1; i >= 0; i--) { 283 assertNull(controls[i].getLeash()); 284 } 285 286 assertTrue(mDisplayContent.getInsetsStateController().getRawInsetsState() 287 .isSourceOrDefaultVisible(statusBarId, statusBars())); 288 assertTrue(mDisplayContent.getInsetsStateController().getRawInsetsState() 289 .isSourceOrDefaultVisible(navBarId, navigationBars())); 290 } 291 292 @SetupWindows(addWindows = W_ACTIVITY) 293 @Test testShowTransientBars_statusBarCanBeTransient_appGetsStatusBarFakeControl()294 public void testShowTransientBars_statusBarCanBeTransient_appGetsStatusBarFakeControl() { 295 addStatusBar().getControllableInsetProvider().getSource().setVisible(false); 296 addNavigationBar().getControllableInsetProvider().setServerVisible(true); 297 298 final InsetsPolicy policy = mDisplayContent.getInsetsPolicy(); 299 policy.updateBarControlTarget(mAppWindow); 300 policy.showTransient(navigationBars() | statusBars(), 301 true /* isGestureOnSystemBar */); 302 waitUntilWindowAnimatorIdle(); 303 assertTrue(policy.isTransient(statusBars())); 304 assertFalse(policy.isTransient(navigationBars())); 305 final InsetsSourceControl[] controls = 306 mDisplayContent.getInsetsStateController().getControlsForDispatch(mAppWindow); 307 308 // The app must get the fake control of the status bar, and must get the real control of the 309 // navigation bar. 310 assertEquals(2, controls.length); 311 for (int i = controls.length - 1; i >= 0; i--) { 312 final InsetsSourceControl control = controls[i]; 313 if (control.getType() == statusBars()) { 314 assertNull(controls[i].getLeash()); 315 } else { 316 assertNotNull(controls[i].getLeash()); 317 } 318 } 319 } 320 321 @SetupWindows(addWindows = W_ACTIVITY) 322 @Test testAbortTransientBars_bothCanBeAborted_appGetsBothRealControls()323 public void testAbortTransientBars_bothCanBeAborted_appGetsBothRealControls() { 324 final InsetsSource statusBarSource = 325 addStatusBar().getControllableInsetProvider().getSource(); 326 final InsetsSource navBarSource = 327 addNavigationBar().getControllableInsetProvider().getSource(); 328 statusBarSource.setVisible(false); 329 navBarSource.setVisible(false); 330 mAppWindow.setRequestedVisibleTypes(0, navigationBars() | statusBars()); 331 mAppWindow.mAboveInsetsState.addSource(navBarSource); 332 mAppWindow.mAboveInsetsState.addSource(statusBarSource); 333 final InsetsPolicy policy = mDisplayContent.getInsetsPolicy(); 334 policy.updateBarControlTarget(mAppWindow); 335 policy.showTransient(navigationBars() | statusBars(), 336 true /* isGestureOnSystemBar */); 337 waitUntilWindowAnimatorIdle(); 338 InsetsSourceControl[] controls = 339 mDisplayContent.getInsetsStateController().getControlsForDispatch(mAppWindow); 340 341 // The app must get both fake controls. 342 assertEquals(2, controls.length); 343 for (int i = controls.length - 1; i >= 0; i--) { 344 assertNull(controls[i].getLeash()); 345 } 346 347 final InsetsState state = mAppWindow.getInsetsState(); 348 state.setSourceVisible(statusBarSource.getId(), true); 349 state.setSourceVisible(navBarSource.getId(), true); 350 351 final InsetsState clientState = mAppWindow.getInsetsState(); 352 // The transient bar states for client should be invisible. 353 assertFalse(clientState.isSourceOrDefaultVisible(statusBarSource.getId(), statusBars())); 354 assertFalse(clientState.isSourceOrDefaultVisible(navBarSource.getId(), navigationBars())); 355 // The original state shouldn't be modified. 356 assertTrue(state.isSourceOrDefaultVisible(statusBarSource.getId(), statusBars())); 357 assertTrue(state.isSourceOrDefaultVisible(navBarSource.getId(), navigationBars())); 358 359 mAppWindow.setRequestedVisibleTypes( 360 navigationBars() | statusBars(), navigationBars() | statusBars()); 361 policy.onRequestedVisibleTypesChanged(mAppWindow); 362 waitUntilWindowAnimatorIdle(); 363 364 controls = mDisplayContent.getInsetsStateController().getControlsForDispatch(mAppWindow); 365 366 // The app must get both real controls. 367 assertEquals(2, controls.length); 368 for (int i = controls.length - 1; i >= 0; i--) { 369 assertNotNull(controls[i].getLeash()); 370 } 371 } 372 373 @Test testShowTransientBars_abortsWhenControlTargetChanges()374 public void testShowTransientBars_abortsWhenControlTargetChanges() { 375 addStatusBar().getControllableInsetProvider().getSource().setVisible(false); 376 addNavigationBar().getControllableInsetProvider().getSource().setVisible(false); 377 final WindowState app = addWindow(TYPE_APPLICATION, "app"); 378 final WindowState app2 = addWindow(TYPE_APPLICATION, "app"); 379 380 final InsetsPolicy policy = mDisplayContent.getInsetsPolicy(); 381 policy.updateBarControlTarget(app); 382 policy.showTransient(navigationBars() | statusBars(), 383 true /* isGestureOnSystemBar */); 384 final InsetsSourceControl[] controls = 385 mDisplayContent.getInsetsStateController().getControlsForDispatch(app); 386 policy.updateBarControlTarget(app2); 387 assertFalse(policy.isTransient(statusBars())); 388 assertFalse(policy.isTransient(navigationBars())); 389 } 390 391 @Test testFakeControlTarget_overrideVisibilityReceivedByWindows()392 public void testFakeControlTarget_overrideVisibilityReceivedByWindows() { 393 final WindowState statusBar = addStatusBar(); 394 final InsetsSourceProvider statusBarProvider = statusBar.getControllableInsetProvider(); 395 statusBar.mSession.mCanForceShowingInsets = true; 396 statusBar.setHasSurface(true); 397 statusBarProvider.setServerVisible(true); 398 399 final InsetsSource statusBarSource = statusBarProvider.getSource(); 400 final int statusBarId = statusBarSource.getId(); 401 assertTrue(statusBarSource.isVisible()); 402 403 final WindowState app1 = addWindow(TYPE_APPLICATION, "app1"); 404 app1.mAboveInsetsState.addSource(statusBarSource); 405 assertTrue(app1.getInsetsState().peekSource(statusBarId).isVisible()); 406 407 final WindowState app2 = addWindow(TYPE_APPLICATION, "app2"); 408 app2.mAboveInsetsState.addSource(statusBarSource); 409 assertTrue(app2.getInsetsState().peekSource(statusBarId).isVisible()); 410 411 app2.setRequestedVisibleTypes(0, navigationBars() | statusBars()); 412 mDisplayContent.getInsetsPolicy().updateBarControlTarget(app2); 413 waitUntilWindowAnimatorIdle(); 414 415 // app2 is the real control target now. It can override the visibility of all sources that 416 // it controls. 417 assertFalse(statusBarSource.isVisible()); 418 assertFalse(app1.getInsetsState().peekSource(statusBarId).isVisible()); 419 assertFalse(app2.getInsetsState().peekSource(statusBarId).isVisible()); 420 421 statusBar.mAttrs.forciblyShownTypes = statusBars(); 422 mDisplayContent.getDisplayPolicy().applyPostLayoutPolicyLw( 423 statusBar, statusBar.mAttrs, null, null); 424 mDisplayContent.getInsetsPolicy().updateBarControlTarget(app2); 425 waitUntilWindowAnimatorIdle(); 426 427 // app2 is the fake control target now. It can only override the visibility of sources 428 // received by windows, but not the raw source. 429 assertTrue(statusBarSource.isVisible()); 430 assertFalse(app1.getInsetsState().peekSource(statusBarId).isVisible()); 431 assertFalse(app2.getInsetsState().peekSource(statusBarId).isVisible()); 432 433 } 434 addNavigationBar()435 private WindowState addNavigationBar() { 436 final Binder owner = new Binder(); 437 final WindowState win = createWindow(null, TYPE_NAVIGATION_BAR, "navBar"); 438 win.mAttrs.flags |= FLAG_NOT_FOCUSABLE; 439 win.mAttrs.providedInsets = new InsetsFrameProvider[] { 440 new InsetsFrameProvider(owner, 0, WindowInsets.Type.navigationBars()), 441 new InsetsFrameProvider(owner, 0, WindowInsets.Type.tappableElement()), 442 new InsetsFrameProvider(owner, 0, WindowInsets.Type.mandatorySystemGestures()) 443 }; 444 mDisplayContent.getDisplayPolicy().addWindowLw(win, win.mAttrs); 445 return win; 446 } 447 addStatusBar()448 private WindowState addStatusBar() { 449 final Binder owner = new Binder(); 450 final WindowState win = createWindow(null, TYPE_STATUS_BAR, "statusBar"); 451 win.mAttrs.flags |= FLAG_NOT_FOCUSABLE; 452 win.mAttrs.providedInsets = new InsetsFrameProvider[] { 453 new InsetsFrameProvider(owner, 0, WindowInsets.Type.statusBars()), 454 new InsetsFrameProvider(owner, 0, WindowInsets.Type.tappableElement()), 455 new InsetsFrameProvider(owner, 0, WindowInsets.Type.mandatorySystemGestures()) 456 }; 457 mDisplayContent.getDisplayPolicy().addWindowLw(win, win.mAttrs); 458 return win; 459 } 460 addWindow(int type, String name)461 private WindowState addWindow(int type, String name) { 462 final WindowState win = createWindow(null, type, name); 463 mDisplayContent.getDisplayPolicy().addWindowLw(win, win.mAttrs); 464 return win; 465 } 466 addAppWindowAndGetControlsForDispatch()467 private InsetsSourceControl[] addAppWindowAndGetControlsForDispatch() { 468 return addWindowAndGetControlsForDispatch(addWindow(TYPE_APPLICATION, "app")); 469 } 470 addWindowAndGetControlsForDispatch(WindowState win)471 private InsetsSourceControl[] addWindowAndGetControlsForDispatch(WindowState win) { 472 mDisplayContent.getDisplayPolicy().addWindowLw(win, win.mAttrs); 473 // Force update the focus in DisplayPolicy here. Otherwise, without server side focus 474 // update, the policy relying on windowing type will never get updated. 475 mDisplayContent.getDisplayPolicy().focusChangedLw(null, win); 476 mDisplayContent.getInsetsPolicy().updateBarControlTarget(win); 477 return mDisplayContent.getInsetsStateController().getControlsForDispatch(win); 478 } 479 } 480