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