1 /*
2  * Copyright (C) 2016 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.view.InsetsSource.ID_IME;
20 import static android.view.WindowManager.LayoutParams.FIRST_SUB_WINDOW;
21 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
22 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
23 import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR;
24 import static android.view.WindowManager.LayoutParams.TYPE_TOAST;
25 
26 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
27 import static com.android.server.policy.WindowManagerPolicy.TRANSIT_EXIT;
28 
29 import static org.junit.Assert.assertEquals;
30 import static org.junit.Assert.assertFalse;
31 import static org.junit.Assert.assertNotEquals;
32 import static org.junit.Assert.assertNotNull;
33 import static org.junit.Assert.assertNull;
34 import static org.junit.Assert.assertTrue;
35 import static org.mockito.Mockito.mock;
36 import static org.mockito.Mockito.verify;
37 
38 import android.content.res.Configuration;
39 import android.os.Bundle;
40 import android.os.IBinder;
41 import android.platform.test.annotations.Presubmit;
42 import android.view.WindowInsets;
43 import android.window.WindowContext;
44 
45 import androidx.test.filters.SmallTest;
46 
47 import org.junit.Test;
48 import org.junit.runner.RunWith;
49 import org.mockito.Mockito;
50 
51 import java.util.function.BiFunction;
52 
53 /**
54  * Tests for the {@link WindowToken} class.
55  *
56  * Build/Install/Run:
57  *  atest WmTests:WindowTokenTests
58  */
59 @SmallTest
60 @Presubmit
61 @RunWith(WindowTestRunner.class)
62 public class WindowTokenTests extends WindowTestsBase {
63 
64     @Test
testAddWindow()65     public void testAddWindow() {
66         final TestWindowToken token = createTestWindowToken(0, mDisplayContent);
67 
68         assertEquals(0, token.getWindowsCount());
69 
70         final WindowState window1 = createWindow(null, TYPE_APPLICATION, token, "window1");
71         final WindowState window11 = createWindow(window1, FIRST_SUB_WINDOW, token, "window11");
72         final WindowState window12 = createWindow(window1, FIRST_SUB_WINDOW, token, "window12");
73         final WindowState window2 = createWindow(null, TYPE_APPLICATION, token, "window2");
74         final WindowState window3 = createWindow(null, TYPE_APPLICATION, token, "window3");
75 
76         token.addWindow(window1);
77         // NOTE: Child windows will not be added to the token as window containers can only
78         // contain/reference their direct children.
79         token.addWindow(window11);
80         token.addWindow(window12);
81         token.addWindow(window2);
82         token.addWindow(window3);
83 
84         // Should not contain the child windows that were added above.
85         assertEquals(3, token.getWindowsCount());
86         assertTrue(token.hasWindow(window1));
87         assertFalse(token.hasWindow(window11));
88         assertFalse(token.hasWindow(window12));
89         assertTrue(token.hasWindow(window2));
90         assertTrue(token.hasWindow(window3));
91 
92         // The child windows should have the same window token as their parents.
93         assertEquals(window1.mToken, window11.mToken);
94         assertEquals(window1.mToken, window12.mToken);
95     }
96 
97     @Test
testAddWindow_assignsLayers()98     public void testAddWindow_assignsLayers() {
99         final TestWindowToken token1 = createTestWindowToken(0, mDisplayContent);
100         final TestWindowToken token2 = createTestWindowToken(0, mDisplayContent);
101         final WindowState window1 = createWindow(null, TYPE_STATUS_BAR, token1, "window1");
102         final WindowState window2 = createWindow(null, TYPE_STATUS_BAR, token2, "window2");
103 
104         token1.addWindow(window1);
105         token2.addWindow(window2);
106 
107         assertEquals(token1.getLastLayer(), 0);
108         assertEquals(token2.getLastLayer(), 1);
109     }
110 
111     @Test
testChildRemoval()112     public void testChildRemoval() {
113         final DisplayContent dc = mDisplayContent;
114         final TestWindowToken token = createTestWindowToken(0, dc);
115 
116         assertEquals(token, dc.getWindowToken(token.token));
117 
118         final WindowState window1 = createWindow(null, TYPE_APPLICATION, token, "window1");
119         final WindowState window2 = createWindow(null, TYPE_APPLICATION, token, "window2");
120 
121         window2.removeImmediately();
122         // The token should still be mapped in the display content since it still has a child.
123         assertEquals(token, dc.getWindowToken(token.token));
124 
125         window1.removeImmediately();
126         // The token should have been removed from the display content since it no longer has a
127         // child.
128         assertEquals(null, dc.getWindowToken(token.token));
129     }
130 
131     /**
132      * Test that a window token isn't orphaned by the system when it is requested to be removed.
133      * Tokens should only be removed from the system when all their windows are gone.
134      */
135     @Test
testTokenRemovalProcess()136     public void testTokenRemovalProcess() {
137         final TestWindowToken token = createTestWindowToken(
138                 TYPE_TOAST, mDisplayContent, true /* persistOnEmpty */);
139 
140         // Verify that the token is on the display
141         assertNotNull(mDisplayContent.getWindowToken(token.token));
142 
143         final WindowState window1 = createWindow(null, TYPE_TOAST, token, "window1");
144         final WindowState window2 = createWindow(null, TYPE_TOAST, token, "window2");
145 
146         mDisplayContent.removeWindowToken(token.token, true /* animateExit */);
147         // Verify that the token is no longer mapped on the display
148         assertNull(mDisplayContent.getWindowToken(token.token));
149         // Verify that the token is still attached to its parent
150         assertNotNull(token.getParent());
151         // Verify that the token windows are still around.
152         assertEquals(2, token.getWindowsCount());
153 
154         window1.removeImmediately();
155         // Verify that the token is still attached to its parent
156         assertNotNull(token.getParent());
157         // Verify that the other token window is still around.
158         assertEquals(1, token.getWindowsCount());
159 
160         window2.removeImmediately();
161         // Verify that the token is no-longer attached to its parent
162         assertNull(token.getParent());
163         // Verify that the token windows are no longer attached to it.
164         assertEquals(0, token.getWindowsCount());
165     }
166 
167     @Test
testFinishFixedRotationTransform()168     public void testFinishFixedRotationTransform() {
169         final WindowToken[] tokens = new WindowToken[3];
170         for (int i = 0; i < tokens.length; i++) {
171             tokens[i] = createTestWindowToken(TYPE_APPLICATION_OVERLAY, mDisplayContent);
172         }
173 
174         final Configuration config = new Configuration(mDisplayContent.getConfiguration());
175         final int originalRotation = config.windowConfiguration.getRotation();
176         final int targetRotation = (originalRotation + 1) % 4;
177 
178         config.windowConfiguration.setRotation(targetRotation);
179         tokens[0].applyFixedRotationTransform(mDisplayInfo, mDisplayContent.mDisplayFrames, config);
180         tokens[1].linkFixedRotationTransform(tokens[0]);
181 
182         // The window tokens should apply the rotation by the transformation.
183         assertEquals(targetRotation, tokens[0].getWindowConfiguration().getRotation());
184         assertEquals(targetRotation, tokens[1].getWindowConfiguration().getRotation());
185 
186         tokens[2].applyFixedRotationTransform(mDisplayInfo, mDisplayContent.mDisplayFrames, config);
187         // The tokens[1] was linked to tokens[0], this should make tokens[1] link to tokens[2].
188         tokens[1].linkFixedRotationTransform(tokens[2]);
189 
190         // Assume the display doesn't rotate, the transformation will be canceled.
191         tokens[0].finishFixedRotationTransform();
192 
193         // The tokens[0] should restore to the original rotation.
194         assertEquals(originalRotation, tokens[0].getWindowConfiguration().getRotation());
195         // The tokens[1] is linked to tokens[2], it should keep the target rotation.
196         assertNotEquals(originalRotation, tokens[1].getWindowConfiguration().getRotation());
197 
198         tokens[2].finishFixedRotationTransform();
199         // The rotation of tokens[1] should be restored because its linked state is finished.
200         assertEquals(originalRotation, tokens[1].getWindowConfiguration().getRotation());
201     }
202 
203     /**
204      * Test that {@link android.view.SurfaceControl} should not be created for the
205      * {@link WindowToken} which was created for {@link WindowContext} initially, the
206      * surface should be create after addWindow for this token.
207      */
208     @Test
testSurfaceCreatedForWindowToken()209     public void testSurfaceCreatedForWindowToken() {
210         final WindowToken fromClientToken = new WindowToken.Builder(mDisplayContent.mWmService,
211                 mock(IBinder.class), TYPE_APPLICATION_OVERLAY)
212                 .setDisplayContent(mDisplayContent)
213                 .setFromClientToken(true)
214                 .build();
215 
216         assertNull(fromClientToken.mSurfaceControl);
217 
218         createWindow(null, TYPE_APPLICATION_OVERLAY, fromClientToken, "window");
219         assertNotNull(fromClientToken.mSurfaceControl);
220 
221         final WindowToken nonClientToken = new WindowToken.Builder(mDisplayContent.mWmService,
222                 mock(IBinder.class), TYPE_APPLICATION_OVERLAY)
223                 .setDisplayContent(mDisplayContent)
224                 .setFromClientToken(false)
225                 .build();
226         assertNotNull(nonClientToken.mSurfaceControl);
227     }
228 
229     @Test
testWindowAttachedWithOptions()230     public void testWindowAttachedWithOptions() {
231         BiFunction<Integer, Bundle, RootDisplayArea> selectFunc =
232                 ((DisplayAreaPolicyBuilder.Result) mDisplayContent.mDisplayAreaPolicy)
233                         .mSelectRootForWindowFunc;
234         spyOn(selectFunc);
235 
236         final WindowToken token1 = new WindowToken.Builder(mDisplayContent.mWmService,
237                 mock(IBinder.class), TYPE_STATUS_BAR)
238                 .setDisplayContent(mDisplayContent)
239                 .setPersistOnEmpty(true)
240                 .setOwnerCanManageAppTokens(true)
241                 .build();
242 
243         verify(selectFunc).apply(token1.windowType, null);
244 
245         final Bundle options = new Bundle();
246         final WindowToken token2 = new WindowToken.Builder(mDisplayContent.mWmService,
247                 mock(IBinder.class), TYPE_STATUS_BAR)
248                 .setDisplayContent(mDisplayContent)
249                 .setPersistOnEmpty(true)
250                 .setOwnerCanManageAppTokens(true)
251                 .setOptions(options)
252                 .build();
253 
254         verify(selectFunc).apply(token2.windowType, options);
255     }
256 
257     /**
258      * Test that {@link WindowToken#setInsetsFrozen(boolean)} will set the frozen insets
259      * states for its children windows and by default it shouldn't let IME window setting
260      * the frozen insets state even the window of the window token is the IME layering target.
261      */
262     @SetupWindows(addWindows = W_INPUT_METHOD)
263     @Test
testSetInsetsFrozen_notAffectImeWindowState()264     public void testSetInsetsFrozen_notAffectImeWindowState() {
265         // Pre-condition: make the IME window be controlled by IME insets provider.
266         mDisplayContent.getInsetsStateController()
267                 .getOrCreateSourceProvider(ID_IME, WindowInsets.Type.ime())
268                 .setWindowContainer(mDisplayContent.mInputMethodWindow, null, null);
269 
270         // Simulate an app window to be the IME layering target, assume the app window has no
271         // frozen insets state by default.
272         final WindowState app = createWindow(null, TYPE_APPLICATION, "app");
273         mDisplayContent.setImeLayeringTarget(app);
274         assertNull(app.getFrozenInsetsState());
275         assertTrue(app.isImeLayeringTarget());
276 
277         // Verify invoking setInsetsFrozen shouldn't let IME window setting the frozen insets state.
278         app.mToken.setInsetsFrozen(true);
279         assertNotNull(app.getFrozenInsetsState());
280         assertNull(mDisplayContent.mInputMethodWindow.getFrozenInsetsState());
281     }
282 
283     @Test
testRemoveWindowToken_noAnimateExitWhenSet()284     public void testRemoveWindowToken_noAnimateExitWhenSet() {
285         final TestWindowToken token = createTestWindowToken(0, mDisplayContent);
286         final WindowState win = createWindow(null, TYPE_APPLICATION, token, "win");
287         makeWindowVisible(win);
288         assertTrue(win.isOnScreen());
289         spyOn(win);
290         spyOn(win.mWinAnimator);
291         spyOn(win.mToken);
292 
293         // Invoking removeWindowToken with setting no window exit animation and not remove window
294         // immediately. verify the window will hide without applying exit animation.
295         mWm.removeWindowToken(win.mToken.token, false /* removeWindows */, false /* animateExit */,
296                 mDisplayContent.mDisplayId);
297         verify(win).onSetAppExiting(Mockito.eq(false) /* animateExit */);
298         verify(win).hide(false /* doAnimation */, false /* requestAnim */);
299         assertFalse(win.isOnScreen());
300         verify(win.mWinAnimator, Mockito.never()).applyAnimationLocked(TRANSIT_EXIT, false);
301         assertTrue(win.mToken.hasChild());
302 
303         // Even though the window is being removed afterwards, it won't apply exit animation.
304         win.removeIfPossible();
305         verify(win.mWinAnimator, Mockito.never()).applyAnimationLocked(TRANSIT_EXIT, false);
306     }
307 }
308