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