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