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