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