1 /*
2  * Copyright (C) 2020 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 android.server.wm.insets;
18 
19 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
20 import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
21 import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
22 import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
23 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
24 
25 import static androidx.test.InstrumentationRegistry.getInstrumentation;
26 
27 import static org.junit.Assert.assertEquals;
28 
29 import android.graphics.Insets;
30 import android.graphics.Rect;
31 import android.os.Bundle;
32 import android.platform.test.annotations.Presubmit;
33 import android.server.wm.WindowManagerTestBase;
34 import android.view.View;
35 import android.view.WindowInsets;
36 import android.view.WindowInsets.Side;
37 import android.view.WindowInsets.Type;
38 import android.view.WindowManager;
39 
40 import androidx.annotation.Nullable;
41 
42 import com.android.compatibility.common.util.PollingCheck;
43 
44 import org.junit.Test;
45 
46 /**
47  * Test whether WindowManager performs the correct layout while the app applies the fit-window-
48  * insets APIs.
49  *
50  * Build/Install/Run:
51  *     atest CtsWindowManagerDeviceInsets:WindowInsetsLayoutTests
52  */
53 @Presubmit
54 @android.server.wm.annotation.Group2
55 public class WindowInsetsLayoutTests extends WindowManagerTestBase {
56 
57     private static final long TIMEOUT = 1000; // milliseconds
58 
59     @Test
testSetFitInsetsTypes()60     public void testSetFitInsetsTypes() {
61         // Start the Activity in fullscreen windowing mode for its bounds to match display bounds.
62         final TestActivity activity =
63                 startActivityInWindowingMode(TestActivity.class, WINDOWING_MODE_FULLSCREEN);
64 
65         // Make sure the main window has been laid out.
66         final View mainWindowRoot = activity.getWindow().getDecorView();
67         PollingCheck.waitFor(TIMEOUT, () -> mainWindowRoot.getWidth() > 0);
68 
69         getInstrumentation().runOnMainSync(() -> {
70             activity.assertMatchesWindowBounds();
71         });
72 
73         testSetFitInsetsTypesInner(Type.statusBars(), activity);
74         testSetFitInsetsTypesInner(Type.navigationBars(), activity);
75         testSetFitInsetsTypesInner(Type.systemBars(), activity);
76     }
77 
testSetFitInsetsTypesInner(int types, TestActivity activity)78     private void testSetFitInsetsTypesInner(int types, TestActivity activity) {
79         getInstrumentation().runOnMainSync(() -> {
80             activity.addOverlayWindow(types, Side.all(), false);
81         });
82 
83         // Make sure the overlay window has been laid out.
84         final View overlayWindowRoot = activity.getOverlayWindowRoot();
85         PollingCheck.waitFor(TIMEOUT, () -> overlayWindowRoot.getWidth() > 0);
86 
87         getInstrumentation().runOnMainSync(() -> {
88             final WindowInsets windowInsets = overlayWindowRoot.getRootWindowInsets();
89             final Insets insets = windowInsets.getInsets(types);
90             assertEquals(Insets.NONE, insets);
91             activity.removeOverlayWindow();
92         });
93     }
94 
95     @Test
testSetFitInsetsSides()96     public void testSetFitInsetsSides() {
97         // Start the Activity in fullscreen windowing mode for its bounds to match display bounds.
98         final TestActivity activity =
99                 startActivityInWindowingMode(TestActivity.class, WINDOWING_MODE_FULLSCREEN);
100 
101         // Make sure the main window has been laid out.
102         final View mainWindowRoot = activity.getWindow().getDecorView();
103         PollingCheck.waitFor(TIMEOUT, () -> mainWindowRoot.getWidth() > 0);
104 
105         getInstrumentation().runOnMainSync(() -> {
106             activity.assertMatchesWindowBounds();
107         });
108 
109         testSetFitInsetsSidesInner(Side.LEFT, activity);
110         testSetFitInsetsSidesInner(Side.TOP, activity);
111         testSetFitInsetsSidesInner(Side.RIGHT, activity);
112         testSetFitInsetsSidesInner(Side.BOTTOM, activity);
113     }
114 
testSetFitInsetsSidesInner(int sides, TestActivity activity)115     private void testSetFitInsetsSidesInner(int sides, TestActivity activity) {
116         final int types = Type.systemBars();
117         getInstrumentation().runOnMainSync(() -> {
118             activity.addOverlayWindow(types, sides, false);
119         });
120 
121         // Make sure the overlay window has been laid out.
122         final View overlayWindowRoot = activity.getOverlayWindowRoot();
123         PollingCheck.waitFor(TIMEOUT, () -> overlayWindowRoot.getWidth() > 0);
124 
125         getInstrumentation().runOnMainSync(() -> {
126             final WindowInsets windowInsets = overlayWindowRoot.getRootWindowInsets();
127             final Insets insets = windowInsets.getInsets(types);
128             if ((sides & Side.LEFT) != 0) {
129                 assertEquals(0, insets.left);
130             }
131             if ((sides & Side.TOP) != 0) {
132                 assertEquals(0, insets.top);
133             }
134             if ((sides & Side.RIGHT) != 0) {
135                 assertEquals(0, insets.right);
136             }
137             if ((sides & Side.BOTTOM) != 0) {
138                 assertEquals(0, insets.bottom);
139             }
140             activity.removeOverlayWindow();
141         });
142     }
143 
144     @Test
testSetFitInsetsIgnoringVisibility()145     public void testSetFitInsetsIgnoringVisibility() {
146         // Start the Activity in fullscreen windowing mode for its bounds to match display bounds.
147         final TestActivity activity =
148                 startActivityInWindowingMode(TestActivity.class, WINDOWING_MODE_FULLSCREEN);
149 
150         // Make sure the main window has been laid out.
151         final View mainWindowRoot = activity.getWindow().getDecorView();
152         PollingCheck.waitFor(TIMEOUT, () -> mainWindowRoot.getWidth() > 0);
153 
154         final int types = Type.systemBars();
155         final int sides = Side.all();
156         final int[] locationAndSize1 = new int[4];
157         final int[] locationAndSize2 = new int[4];
158 
159         getInstrumentation().runOnMainSync(() -> {
160             activity.assertMatchesWindowBounds();
161             activity.addOverlayWindow(types, sides, false);
162         });
163 
164         // Make sure the 1st overlay window has been laid out.
165         final View overlayWindowRoot1 = activity.getOverlayWindowRoot();
166         PollingCheck.waitFor(TIMEOUT, () -> overlayWindowRoot1.getWidth() > 0);
167 
168         getInstrumentation().runOnMainSync(() -> {
169             overlayWindowRoot1.getLocationOnScreen(locationAndSize1);
170             locationAndSize1[2] = overlayWindowRoot1.getWidth();
171             locationAndSize1[3] = overlayWindowRoot1.getHeight();
172             activity.removeOverlayWindow();
173 
174             mainWindowRoot.getWindowInsetsController().hide(types);
175 
176             activity.addOverlayWindow(types, sides, true);
177         });
178 
179         // Make sure the 2nd overlay window has been laid out.
180         final View overlayWindowRoot2 = activity.getOverlayWindowRoot();
181         PollingCheck.waitFor(TIMEOUT, () -> overlayWindowRoot2.getWidth() > 0);
182 
183         getInstrumentation().runOnMainSync(() -> {
184             overlayWindowRoot2.getLocationOnScreen(locationAndSize2);
185             locationAndSize2[2] = overlayWindowRoot2.getWidth();
186             locationAndSize2[3] = overlayWindowRoot2.getHeight();
187             activity.removeOverlayWindow();
188         });
189 
190         for (int i = 0; i < 4; i++) {
191             assertEquals(locationAndSize1[i], locationAndSize2[i]);
192         }
193     }
194 
195     public static class TestActivity extends FocusableActivity {
196 
197         private View mOverlayWindowRoot;
198 
199         @Override
onCreate(@ullable Bundle savedInstanceState)200         protected void onCreate(@Nullable Bundle savedInstanceState) {
201             super.onCreate(savedInstanceState);
202             final View view = new View(this);
203             setContentView(view);
204             WindowManager.LayoutParams lp = getWindow().getAttributes();
205             lp.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
206             getWindow().setAttributes(lp);
207         }
208 
addOverlayWindow(int types, int sides, boolean ignoreVis)209         void addOverlayWindow(int types, int sides, boolean ignoreVis) {
210             final WindowManager.LayoutParams attrs = new WindowManager.LayoutParams();
211             // In targets like automotive portrait UI, when the system bars are hidden, the
212             // system can change the bounds of the app to be fullscreen which can cause issues
213             // with the location. Use TYPE_APPLICATION_OVERLAY so that overlay window always ends
214             // up taking the fullscreen bounds.
215             attrs.type = TYPE_APPLICATION_OVERLAY;
216             attrs.width = MATCH_PARENT;
217             attrs.height = MATCH_PARENT;
218             attrs.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
219             attrs.flags = FLAG_NOT_FOCUSABLE;
220             attrs.setFitInsetsTypes(types);
221             attrs.setFitInsetsSides(sides);
222             attrs.setFitInsetsIgnoringVisibility(ignoreVis);
223             mOverlayWindowRoot = new View(this);
224             getWindowManager().addView(mOverlayWindowRoot, attrs);
225         }
226 
removeOverlayWindow()227         void removeOverlayWindow() {
228             getWindowManager().removeViewImmediate(mOverlayWindowRoot);
229         }
230 
getOverlayWindowRoot()231         View getOverlayWindowRoot() {
232             return mOverlayWindowRoot;
233         }
234 
assertMatchesWindowBounds()235         void assertMatchesWindowBounds() {
236             final View rootView = getWindow().getDecorView();
237             final Rect windowMetricsBounds =
238                     getWindowManager().getCurrentWindowMetrics().getBounds();
239             assertEquals(windowMetricsBounds.width(), rootView.getWidth());
240             assertEquals(windowMetricsBounds.height(), rootView.getHeight());
241             final int[] locationOnScreen = new int[2];
242             rootView.getLocationOnScreen(locationOnScreen);
243             assertEquals(locationOnScreen[0] /* expected x */, windowMetricsBounds.left);
244             assertEquals(locationOnScreen[1] /* expected y */, windowMetricsBounds.top);
245         }
246     }
247 }
248