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