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