1 /* 2 * Copyright (C) 2018 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.WINDOWING_MODE_FREEFORM; 20 import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; 21 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; 22 import static android.view.InsetsSource.ID_IME; 23 import static android.view.WindowInsets.Type.ime; 24 import static android.view.WindowInsets.Type.navigationBars; 25 import static android.view.WindowInsets.Type.statusBars; 26 import static android.view.WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM; 27 import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; 28 import static android.view.WindowManager.LayoutParams.LAST_APPLICATION_WINDOW; 29 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION; 30 import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD; 31 32 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; 33 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; 34 import static com.android.server.wm.WindowContainer.POSITION_TOP; 35 36 import static org.junit.Assert.assertEquals; 37 import static org.junit.Assert.assertFalse; 38 import static org.junit.Assert.assertNotNull; 39 import static org.junit.Assert.assertNull; 40 import static org.junit.Assert.assertTrue; 41 import static org.mockito.ArgumentMatchers.any; 42 import static org.mockito.ArgumentMatchers.anyBoolean; 43 import static org.mockito.Mockito.atLeastOnce; 44 import static org.mockito.Mockito.clearInvocations; 45 import static org.mockito.Mockito.spy; 46 import static org.mockito.Mockito.verify; 47 48 import android.graphics.Rect; 49 import android.platform.test.annotations.Presubmit; 50 import android.util.SparseArray; 51 import android.view.InsetsSource; 52 import android.view.InsetsSourceControl; 53 import android.view.InsetsState; 54 55 import androidx.test.filters.SmallTest; 56 57 import com.android.internal.util.function.TriFunction; 58 59 import org.junit.Test; 60 import org.junit.runner.RunWith; 61 62 @SmallTest 63 @Presubmit 64 @RunWith(WindowTestRunner.class) 65 public class InsetsStateControllerTest extends WindowTestsBase { 66 67 private static final int ID_STATUS_BAR = 68 InsetsSource.createId(null /* owner */, 0 /* index */, statusBars()); 69 private static final int ID_NAVIGATION_BAR = 70 InsetsSource.createId(null /* owner */, 0 /* index */, navigationBars()); 71 private static final int ID_CLIMATE_BAR = 72 InsetsSource.createId(null /* owner */, 1 /* index */, statusBars()); 73 private static final int ID_EXTRA_NAVIGATION_BAR = 74 InsetsSource.createId(null /* owner */, 1 /* index */, navigationBars()); 75 76 @Test testStripForDispatch_navBar()77 public void testStripForDispatch_navBar() { 78 final WindowState navBar = createWindow(null, TYPE_APPLICATION, "navBar"); 79 final WindowState statusBar = createWindow(null, TYPE_APPLICATION, "statusBar"); 80 final WindowState ime = createWindow(null, TYPE_APPLICATION, "ime"); 81 82 // IME cannot be the IME target. 83 ime.mAttrs.flags |= FLAG_NOT_FOCUSABLE; 84 85 getController().getOrCreateSourceProvider(ID_STATUS_BAR, statusBars()) 86 .setWindowContainer(statusBar, null, null); 87 getController().getOrCreateSourceProvider(ID_NAVIGATION_BAR, navigationBars()) 88 .setWindowContainer(navBar, null, null); 89 getController().getOrCreateSourceProvider(ID_IME, ime()) 90 .setWindowContainer(ime, null, null); 91 92 assertNull(navBar.getInsetsState().peekSource(ID_IME)); 93 assertNull(navBar.getInsetsState().peekSource(ID_STATUS_BAR)); 94 } 95 96 @Test testStripForDispatch_pip()97 public void testStripForDispatch_pip() { 98 final WindowState statusBar = createWindow(null, TYPE_APPLICATION, "statusBar"); 99 final WindowState navBar = createWindow(null, TYPE_APPLICATION, "navBar"); 100 final WindowState app = createWindow(null, TYPE_APPLICATION, "app"); 101 102 getController().getOrCreateSourceProvider(ID_STATUS_BAR, statusBars()) 103 .setWindowContainer(statusBar, null, null); 104 getController().getOrCreateSourceProvider(ID_NAVIGATION_BAR, navigationBars()) 105 .setWindowContainer(navBar, null, null); 106 app.setWindowingMode(WINDOWING_MODE_PINNED); 107 108 assertNull(app.getInsetsState().peekSource(ID_STATUS_BAR)); 109 assertNull(app.getInsetsState().peekSource(ID_NAVIGATION_BAR)); 110 assertNull(app.getInsetsState().peekSource(ID_IME)); 111 } 112 113 @Test testStripForDispatch_freeform()114 public void testStripForDispatch_freeform() { 115 final WindowState statusBar = createWindow(null, TYPE_APPLICATION, "statusBar"); 116 final WindowState navBar = createWindow(null, TYPE_APPLICATION, "navBar"); 117 final WindowState app = createWindow(null, TYPE_APPLICATION, "app"); 118 119 getController().getOrCreateSourceProvider(ID_STATUS_BAR, statusBars()) 120 .setWindowContainer(statusBar, null, null); 121 getController().getOrCreateSourceProvider(ID_NAVIGATION_BAR, navigationBars()) 122 .setWindowContainer(navBar, null, null); 123 app.setWindowingMode(WINDOWING_MODE_FREEFORM); 124 125 assertNull(app.getInsetsState().peekSource(ID_STATUS_BAR)); 126 assertNull(app.getInsetsState().peekSource(ID_NAVIGATION_BAR)); 127 } 128 129 @Test testStripForDispatch_multiwindow_alwaysOnTop()130 public void testStripForDispatch_multiwindow_alwaysOnTop() { 131 final WindowState statusBar = createWindow(null, TYPE_APPLICATION, "statusBar"); 132 final WindowState navBar = createWindow(null, TYPE_APPLICATION, "navBar"); 133 final WindowState app = createWindow(null, TYPE_APPLICATION, "app"); 134 135 getController().getOrCreateSourceProvider(ID_STATUS_BAR, statusBars()) 136 .setWindowContainer(statusBar, null, null); 137 getController().getOrCreateSourceProvider(ID_NAVIGATION_BAR, navigationBars()) 138 .setWindowContainer(navBar, null, null); 139 app.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW); 140 app.setAlwaysOnTop(true); 141 142 assertNull(app.getInsetsState().peekSource(ID_STATUS_BAR)); 143 assertNull(app.getInsetsState().peekSource(ID_NAVIGATION_BAR)); 144 } 145 146 @SetupWindows(addWindows = W_INPUT_METHOD) 147 @Test testStripForDispatch_independentSources()148 public void testStripForDispatch_independentSources() { 149 getController().getOrCreateSourceProvider(ID_IME, ime()) 150 .setWindowContainer(mImeWindow, null, null); 151 152 final WindowState app1 = createWindow(null, TYPE_APPLICATION, "app1"); 153 final WindowState app2 = createWindow(null, TYPE_APPLICATION, "app2"); 154 155 app1.mAboveInsetsState.addSource(getController().getRawInsetsState().peekSource(ID_IME)); 156 157 getController().getRawInsetsState().setSourceVisible(ID_IME, true); 158 assertFalse(app2.getInsetsState().isSourceOrDefaultVisible(ID_IME, ime())); 159 assertTrue(app1.getInsetsState().isSourceOrDefaultVisible(ID_IME, ime())); 160 } 161 162 @SetupWindows(addWindows = W_INPUT_METHOD) 163 @Test testStripForDispatch_belowIme()164 public void testStripForDispatch_belowIme() { 165 getController().getOrCreateSourceProvider(ID_IME, ime()) 166 .setWindowContainer(mImeWindow, null, null); 167 168 final WindowState app = createWindow(null, TYPE_APPLICATION, "app"); 169 app.mAboveInsetsState.getOrCreateSource(ID_IME, ime()) 170 .setVisible(true) 171 .setFrame(mImeWindow.getFrame()); 172 173 getController().getRawInsetsState().setSourceVisible(ID_IME, true); 174 assertTrue(app.getInsetsState().isSourceOrDefaultVisible(ID_IME, ime())); 175 } 176 177 @SetupWindows(addWindows = W_INPUT_METHOD) 178 @Test testStripForDispatch_aboveIme()179 public void testStripForDispatch_aboveIme() { 180 getController().getOrCreateSourceProvider(ID_IME, ime()) 181 .setWindowContainer(mImeWindow, null, null); 182 183 final WindowState app = createWindow(null, TYPE_APPLICATION, "app"); 184 185 getController().getRawInsetsState().setSourceVisible(ID_IME, true); 186 assertFalse(app.getInsetsState().isSourceOrDefaultVisible(ID_IME, ime())); 187 } 188 189 @SetupWindows(addWindows = W_INPUT_METHOD) 190 @Test testStripForDispatch_imeOrderChanged()191 public void testStripForDispatch_imeOrderChanged() { 192 // This can be the IME z-order target while app cannot be the IME z-order target. 193 // This is also the only IME control target in this test, so IME won't be invisible caused 194 // by the control-target change. 195 final WindowState base = createWindow(null, TYPE_APPLICATION, "base"); 196 mDisplayContent.updateImeInputAndControlTarget(base); 197 198 // Make IME and stay visible during the test. 199 mImeWindow.setHasSurface(true); 200 getController().getOrCreateSourceProvider(ID_IME, ime()) 201 .setWindowContainer(mImeWindow, null, null); 202 getController().onImeControlTargetChanged(base); 203 base.setRequestedVisibleTypes(ime(), ime()); 204 getController().onRequestedVisibleTypesChanged(base); 205 206 // Send our spy window (app) into the system so that we can detect the invocation. 207 final WindowState win = createWindow(null, TYPE_APPLICATION, "app"); 208 win.setHasSurface(true); 209 final WindowToken parent = win.mToken; 210 parent.removeChild(win); 211 final WindowState app = spy(win); 212 parent.addWindow(app); 213 214 // Adding FLAG_NOT_FOCUSABLE makes app above IME. 215 app.mAttrs.flags |= FLAG_NOT_FOCUSABLE; 216 mDisplayContent.computeImeTarget(true); 217 mDisplayContent.applySurfaceChangesTransaction(); 218 219 // app won't get visible IME insets while above IME even when IME is visible. 220 assertTrue(getController().getRawInsetsState().isSourceOrDefaultVisible(ID_IME, ime())); 221 assertFalse(app.getInsetsState().isSourceOrDefaultVisible(ID_IME, ime())); 222 223 // Reset invocation counter. 224 clearInvocations(app); 225 226 // Removing FLAG_NOT_FOCUSABLE makes app below IME. 227 app.mAttrs.flags &= ~FLAG_NOT_FOCUSABLE; 228 mDisplayContent.computeImeTarget(true); 229 mDisplayContent.applySurfaceChangesTransaction(); 230 app.mAboveInsetsState.getOrCreateSource(ID_IME, ime()) 231 .setVisible(true) 232 .setFrame(mImeWindow.getFrame()); 233 234 // Make sure app got notified. 235 verify(app, atLeastOnce()).notifyInsetsChanged(); 236 237 // app will get visible IME insets while below IME. 238 assertTrue(app.getInsetsState().isSourceOrDefaultVisible(ID_IME, ime())); 239 } 240 241 @SetupWindows(addWindows = W_INPUT_METHOD) 242 @Test testStripForDispatch_childWindow_altFocusable()243 public void testStripForDispatch_childWindow_altFocusable() { 244 getController().getOrCreateSourceProvider(ID_IME, ime()) 245 .setWindowContainer(mImeWindow, null, null); 246 247 final WindowState app = createWindow(null, TYPE_APPLICATION, "app"); 248 final WindowState child = createWindow(app, TYPE_APPLICATION, "child"); 249 app.mAboveInsetsState.set(getController().getRawInsetsState()); 250 child.mAboveInsetsState.set(getController().getRawInsetsState()); 251 child.mAttrs.flags |= FLAG_ALT_FOCUSABLE_IM; 252 253 mDisplayContent.computeImeTarget(true); 254 mDisplayContent.setLayoutNeeded(); 255 mDisplayContent.applySurfaceChangesTransaction(); 256 257 getController().getRawInsetsState().setSourceVisible(ID_IME, true); 258 assertTrue(app.getInsetsState().isSourceOrDefaultVisible(ID_IME, ime())); 259 assertFalse(child.getInsetsState().isSourceOrDefaultVisible(ID_IME, ime())); 260 } 261 262 @SetupWindows(addWindows = W_INPUT_METHOD) 263 @Test testStripForDispatch_childWindow_splitScreen()264 public void testStripForDispatch_childWindow_splitScreen() { 265 getController().getOrCreateSourceProvider(ID_IME, ime()) 266 .setWindowContainer(mImeWindow, null, null); 267 268 final WindowState app = createWindow(null, TYPE_APPLICATION, "app"); 269 final WindowState child = createWindow(app, TYPE_APPLICATION, "child"); 270 app.mAboveInsetsState.addSource(getController().getRawInsetsState().peekSource(ID_IME)); 271 child.mAttrs.flags |= FLAG_NOT_FOCUSABLE; 272 child.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW); 273 274 mDisplayContent.computeImeTarget(true); 275 mDisplayContent.setLayoutNeeded(); 276 mDisplayContent.applySurfaceChangesTransaction(); 277 278 getController().getRawInsetsState().setSourceVisible(ID_IME, true); 279 assertTrue(app.getInsetsState().isSourceOrDefaultVisible(ID_IME, ime())); 280 assertFalse(child.getInsetsState().isSourceOrDefaultVisible(ID_IME, ime())); 281 } 282 283 @Test testImeForDispatch()284 public void testImeForDispatch() { 285 final WindowState statusBar = createWindow(null, TYPE_APPLICATION, "statusBar"); 286 final WindowState ime = createWindow(null, TYPE_INPUT_METHOD, "ime"); 287 288 // IME cannot be the IME target. 289 ime.mAttrs.flags |= FLAG_NOT_FOCUSABLE; 290 291 InsetsSourceProvider statusBarProvider = 292 getController().getOrCreateSourceProvider(ID_STATUS_BAR, statusBars()); 293 final SparseArray<TriFunction<DisplayFrames, WindowContainer, Rect, Integer>> 294 imeOverrideProviders = new SparseArray<>(); 295 imeOverrideProviders.put(TYPE_INPUT_METHOD, ((displayFrames, windowState, rect) -> { 296 rect.set(0, 1, 2, 3); 297 return 0; 298 })); 299 statusBarProvider.setWindowContainer(statusBar, null, imeOverrideProviders); 300 getController().getOrCreateSourceProvider(ID_IME, ime()) 301 .setWindowContainer(ime, null, null); 302 statusBar.setControllableInsetProvider(statusBarProvider); 303 statusBar.updateSourceFrame(statusBar.getFrame()); 304 305 statusBarProvider.onPostLayout(); 306 307 final InsetsState state = ime.getInsetsState(); 308 assertEquals(new Rect(0, 1, 2, 3), state.peekSource(ID_STATUS_BAR).getFrame()); 309 } 310 311 @Test testBarControllingWinChanged()312 public void testBarControllingWinChanged() { 313 final WindowState navBar = createWindow(null, TYPE_APPLICATION, "navBar"); 314 final WindowState statusBar = createWindow(null, TYPE_APPLICATION, "statusBar"); 315 final WindowState climateBar = createWindow(null, TYPE_APPLICATION, "climateBar"); 316 final WindowState extraNavBar = createWindow(null, TYPE_APPLICATION, "extraNavBar"); 317 final WindowState app = createWindow(null, TYPE_APPLICATION, "app"); 318 getController().getOrCreateSourceProvider(ID_STATUS_BAR, statusBars()) 319 .setWindowContainer(statusBar, null, null); 320 getController().getOrCreateSourceProvider(ID_NAVIGATION_BAR, navigationBars()) 321 .setWindowContainer(navBar, null, null); 322 getController().getOrCreateSourceProvider(ID_CLIMATE_BAR, statusBars()) 323 .setWindowContainer(climateBar, null, null); 324 getController().getOrCreateSourceProvider(ID_EXTRA_NAVIGATION_BAR, navigationBars()) 325 .setWindowContainer(extraNavBar, null, null); 326 getController().onBarControlTargetChanged(app, null, app, null); 327 InsetsSourceControl[] controls = getController().getControlsForDispatch(app); 328 assertEquals(4, controls.length); 329 } 330 331 @Test testControlRevoked()332 public void testControlRevoked() { 333 final WindowState statusBar = createWindow(null, TYPE_APPLICATION, "statusBar"); 334 final WindowState app = createWindow(null, TYPE_APPLICATION, "app"); 335 getController().getOrCreateSourceProvider(ID_STATUS_BAR, statusBars()) 336 .setWindowContainer(statusBar, null, null); 337 getController().onBarControlTargetChanged(app, null, null, null); 338 assertNotNull(getController().getControlsForDispatch(app)); 339 getController().onBarControlTargetChanged(null, null, null, null); 340 assertNull(getController().getControlsForDispatch(app)); 341 } 342 343 @Test testControlRevoked_animation()344 public void testControlRevoked_animation() { 345 final WindowState statusBar = createWindow(null, TYPE_APPLICATION, "statusBar"); 346 final WindowState app = createWindow(null, TYPE_APPLICATION, "app"); 347 getController().getOrCreateSourceProvider(ID_STATUS_BAR, statusBars()) 348 .setWindowContainer(statusBar, null, null); 349 getController().onBarControlTargetChanged(app, null, null, null); 350 assertNotNull(getController().getControlsForDispatch(app)); 351 statusBar.cancelAnimation(); 352 assertNull(getController().getControlsForDispatch(app)); 353 } 354 355 @Test testTransientVisibilityOfFixedRotationState()356 public void testTransientVisibilityOfFixedRotationState() { 357 final WindowState statusBar = createWindow(null, TYPE_APPLICATION, "statusBar"); 358 final WindowState app = createWindow(null, TYPE_APPLICATION, "app"); 359 final InsetsSourceProvider provider = getController() 360 .getOrCreateSourceProvider(ID_STATUS_BAR, statusBars()); 361 provider.setWindowContainer(statusBar, null, null); 362 363 final InsetsState rotatedState = new InsetsState(app.getInsetsState(), 364 true /* copySources */); 365 rotatedState.getOrCreateSource(ID_STATUS_BAR, statusBars()); 366 spyOn(app.mToken); 367 doReturn(rotatedState).when(app.mToken).getFixedRotationTransformInsetsState(); 368 assertTrue(rotatedState.isSourceOrDefaultVisible(ID_STATUS_BAR, statusBars())); 369 370 app.setRequestedVisibleTypes(0, statusBars()); 371 mDisplayContent.getInsetsPolicy().updateBarControlTarget(app); 372 mDisplayContent.getInsetsPolicy().showTransient(statusBars(), 373 true /* isGestureOnSystemBar */); 374 waitUntilWindowAnimatorIdle(); 375 376 assertTrue(mDisplayContent.getInsetsPolicy().isTransient(statusBars())); 377 assertFalse(app.getInsetsState().isSourceOrDefaultVisible(ID_STATUS_BAR, statusBars())); 378 } 379 380 @Test testUpdateAboveInsetsState_provideInsets()381 public void testUpdateAboveInsetsState_provideInsets() { 382 final WindowState app = createTestWindow("app"); 383 final WindowState statusBar = createTestWindow("statusBar"); 384 final WindowState navBar = createTestWindow("navBar"); 385 386 getController().getOrCreateSourceProvider(ID_STATUS_BAR, statusBars()) 387 .setWindowContainer(statusBar, null, null); 388 389 assertNull(app.mAboveInsetsState.peekSource(ID_STATUS_BAR)); 390 assertNull(statusBar.mAboveInsetsState.peekSource(ID_STATUS_BAR)); 391 assertNull(navBar.mAboveInsetsState.peekSource(ID_STATUS_BAR)); 392 393 getController().updateAboveInsetsState(true /* notifyInsetsChange */); 394 395 assertNotNull(app.mAboveInsetsState.peekSource(ID_STATUS_BAR)); 396 assertNull(statusBar.mAboveInsetsState.peekSource(ID_STATUS_BAR)); 397 assertNull(navBar.mAboveInsetsState.peekSource(ID_STATUS_BAR)); 398 399 verify(app, atLeastOnce()).notifyInsetsChanged(); 400 } 401 402 @Test testUpdateAboveInsetsState_receiveInsets()403 public void testUpdateAboveInsetsState_receiveInsets() { 404 final WindowState app = createTestWindow("app"); 405 final WindowState statusBar = createTestWindow("statusBar"); 406 final WindowState navBar = createTestWindow("navBar"); 407 408 getController().getOrCreateSourceProvider(ID_STATUS_BAR, statusBars()) 409 .setWindowContainer(statusBar, null, null); 410 getController().getOrCreateSourceProvider(ID_NAVIGATION_BAR, navigationBars()) 411 .setWindowContainer(navBar, null, null); 412 413 assertNull(app.mAboveInsetsState.peekSource(ID_STATUS_BAR)); 414 assertNull(app.mAboveInsetsState.peekSource(ID_NAVIGATION_BAR)); 415 416 getController().updateAboveInsetsState(true /* notifyInsetsChange */); 417 418 assertNotNull(app.mAboveInsetsState.peekSource(ID_STATUS_BAR)); 419 assertNotNull(app.mAboveInsetsState.peekSource(ID_NAVIGATION_BAR)); 420 421 verify(app, atLeastOnce()).notifyInsetsChanged(); 422 } 423 424 @Test testUpdateAboveInsetsState_zOrderChanged()425 public void testUpdateAboveInsetsState_zOrderChanged() { 426 final WindowState ime = createNonAppWindow("ime"); 427 final WindowState app = createNonAppWindow("app"); 428 final WindowState statusBar = createNonAppWindow("statusBar"); 429 final WindowState navBar = createNonAppWindow("navBar"); 430 431 final InsetsSourceProvider imeSourceProvider = 432 getController().getOrCreateSourceProvider(ID_IME, ime()); 433 imeSourceProvider.setWindowContainer(ime, null, null); 434 435 waitUntilHandlersIdle(); 436 clearInvocations(mDisplayContent); 437 imeSourceProvider.updateControlForTarget(app, false /* force */); 438 imeSourceProvider.setClientVisible(true); 439 verify(mDisplayContent).assignWindowLayers(anyBoolean()); 440 waitUntilHandlersIdle(); 441 // The visibility change should trigger a traversal to notify the change. 442 verify(mDisplayContent).notifyInsetsChanged(any()); 443 444 getController().getOrCreateSourceProvider(ID_STATUS_BAR, statusBars()) 445 .setWindowContainer(statusBar, null, null); 446 getController().getOrCreateSourceProvider(ID_NAVIGATION_BAR, navigationBars()) 447 .setWindowContainer(navBar, null, null); 448 449 getController().updateAboveInsetsState(false /* notifyInsetsChange */); 450 451 // ime is below others. 452 assertNull(app.mAboveInsetsState.peekSource(ID_IME)); 453 assertNull(statusBar.mAboveInsetsState.peekSource(ID_IME)); 454 assertNull(navBar.mAboveInsetsState.peekSource(ID_IME)); 455 assertNotNull(ime.mAboveInsetsState.peekSource(ID_STATUS_BAR)); 456 assertNotNull(ime.mAboveInsetsState.peekSource(ID_NAVIGATION_BAR)); 457 458 ime.getParent().positionChildAt(POSITION_TOP, ime, true /* includingParents */); 459 getController().updateAboveInsetsState(true /* notifyInsetsChange */); 460 461 // ime is above others. 462 assertNotNull(app.mAboveInsetsState.peekSource(ID_IME)); 463 assertNotNull(statusBar.mAboveInsetsState.peekSource(ID_IME)); 464 assertNotNull(navBar.mAboveInsetsState.peekSource(ID_IME)); 465 assertNull(ime.mAboveInsetsState.peekSource(ID_STATUS_BAR)); 466 assertNull(ime.mAboveInsetsState.peekSource(ID_NAVIGATION_BAR)); 467 468 verify(ime, atLeastOnce()).notifyInsetsChanged(); 469 verify(app, atLeastOnce()).notifyInsetsChanged(); 470 verify(statusBar, atLeastOnce()).notifyInsetsChanged(); 471 verify(navBar, atLeastOnce()).notifyInsetsChanged(); 472 } 473 474 @Test testUpdateAboveInsetsState_imeTargetOnScreenBehavior()475 public void testUpdateAboveInsetsState_imeTargetOnScreenBehavior() { 476 final WindowToken imeToken = createTestWindowToken(TYPE_INPUT_METHOD, mDisplayContent); 477 final WindowState ime = createWindow(null, TYPE_INPUT_METHOD, imeToken, "ime"); 478 final WindowState app = createTestWindow("app"); 479 480 getController().getOrCreateSourceProvider(ID_IME, ime()) 481 .setWindowContainer(ime, null, null); 482 ime.getControllableInsetProvider().setServerVisible(true); 483 484 app.mActivityRecord.setVisibility(true); 485 mDisplayContent.setImeLayeringTarget(app); 486 mDisplayContent.updateImeInputAndControlTarget(app); 487 488 app.setRequestedVisibleTypes(ime(), ime()); 489 getController().onRequestedVisibleTypesChanged(app); 490 assertTrue(ime.getControllableInsetProvider().getSource().isVisible()); 491 492 getController().updateAboveInsetsState(true /* notifyInsetsChange */); 493 assertNotNull(app.getInsetsState().peekSource(ID_IME)); 494 verify(app, atLeastOnce()).notifyInsetsChanged(); 495 496 // Expect the app will still get IME insets even when the app was invisible. 497 // (i.e. app invisible after locking the device) 498 app.mActivityRecord.setVisible(false); 499 app.setHasSurface(false); 500 getController().updateAboveInsetsState(true /* notifyInsetsChange */); 501 assertNotNull(app.getInsetsState().peekSource(ID_IME)); 502 verify(app, atLeastOnce()).notifyInsetsChanged(); 503 504 // Expect the app will get IME insets when the app is requesting visible. 505 // (i.e. app is going to visible when unlocking the device) 506 app.mActivityRecord.setVisibility(true); 507 assertTrue(app.isVisibleRequested()); 508 getController().updateAboveInsetsState(true /* notifyInsetsChange */); 509 assertNotNull(app.getInsetsState().peekSource(ID_IME)); 510 verify(app, atLeastOnce()).notifyInsetsChanged(); 511 } 512 513 @Test testDispatchGlobalInsets()514 public void testDispatchGlobalInsets() { 515 final WindowState navBar = createWindow(null, TYPE_APPLICATION, "navBar"); 516 getController().getOrCreateSourceProvider(ID_NAVIGATION_BAR, navigationBars()) 517 .setWindowContainer(navBar, null, null); 518 final WindowState app = createWindow(null, TYPE_APPLICATION, "app"); 519 assertNull(app.getInsetsState().peekSource(ID_NAVIGATION_BAR)); 520 app.mAttrs.receiveInsetsIgnoringZOrder = true; 521 assertNotNull(app.getInsetsState().peekSource(ID_NAVIGATION_BAR)); 522 } 523 524 @SetupWindows(addWindows = W_INPUT_METHOD) 525 @Test testGetInsetsHintForNewControl()526 public void testGetInsetsHintForNewControl() { 527 final WindowState app1 = createTestWindow("app1"); 528 final WindowState app2 = createTestWindow("app2"); 529 530 makeWindowVisible(mImeWindow); 531 final InsetsSourceProvider imeInsetsProvider = 532 getController().getOrCreateSourceProvider(ID_IME, ime()); 533 imeInsetsProvider.setWindowContainer(mImeWindow, null, null); 534 imeInsetsProvider.updateSourceFrame(mImeWindow.getFrame()); 535 536 imeInsetsProvider.updateControlForTarget(app1, false); 537 imeInsetsProvider.onPostLayout(); 538 final InsetsSourceControl control1 = imeInsetsProvider.getControl(app1); 539 assertNotNull(control1); 540 assertEquals(imeInsetsProvider.getSource().getFrame().height(), 541 control1.getInsetsHint().bottom); 542 543 // Simulate the IME control target updated from app1 to app2 when IME insets was invisible. 544 imeInsetsProvider.setServerVisible(false); 545 imeInsetsProvider.updateControlForTarget(app2, false); 546 547 // Verify insetsHint of the new control is same as last IME source frame after the layout. 548 imeInsetsProvider.onPostLayout(); 549 final InsetsSourceControl control2 = imeInsetsProvider.getControl(app2); 550 assertNotNull(control2); 551 assertEquals(imeInsetsProvider.getSource().getFrame().height(), 552 control2.getInsetsHint().bottom); 553 } 554 555 /** Creates a window which is associated with ActivityRecord. */ createTestWindow(String name)556 private WindowState createTestWindow(String name) { 557 final WindowState win = createWindow(null, TYPE_APPLICATION, name); 558 win.setHasSurface(true); 559 spyOn(win); 560 return win; 561 } 562 563 /** Creates a non-activity window. */ createNonAppWindow(String name)564 private WindowState createNonAppWindow(String name) { 565 final WindowState win = createWindow(null, LAST_APPLICATION_WINDOW + 1, name); 566 win.setHasSurface(true); 567 spyOn(win); 568 return win; 569 } 570 getController()571 private InsetsStateController getController() { 572 return mDisplayContent.getInsetsStateController(); 573 } 574 } 575