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.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; 20 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; 21 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE; 22 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT; 23 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; 24 import static android.view.Display.DEFAULT_DISPLAY; 25 import static android.view.DisplayCutout.fromBoundingRect; 26 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION; 27 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG; 28 import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION; 29 import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR; 30 import static android.view.WindowManager.LayoutParams.TYPE_VOICE_INTERACTION; 31 import static com.android.server.wm.WindowContainer.POSITION_TOP; 32 33 import static org.hamcrest.Matchers.is; 34 import static org.junit.Assert.assertEquals; 35 import static org.junit.Assert.assertFalse; 36 import static org.junit.Assert.assertThat; 37 import static org.junit.Assert.assertTrue; 38 import static org.mockito.Mockito.times; 39 import static org.mockito.Mockito.verify; 40 41 import org.junit.Test; 42 import org.junit.runner.RunWith; 43 44 import android.annotation.SuppressLint; 45 import android.content.res.Configuration; 46 import android.graphics.Rect; 47 import android.os.SystemClock; 48 import android.platform.test.annotations.Presubmit; 49 import android.support.test.filters.FlakyTest; 50 import android.support.test.filters.SmallTest; 51 import android.support.test.runner.AndroidJUnit4; 52 import android.util.DisplayMetrics; 53 import android.util.SparseIntArray; 54 import android.view.DisplayCutout; 55 import android.view.MotionEvent; 56 import android.view.Surface; 57 58 import com.android.server.wm.utils.WmDisplayCutout; 59 60 import java.util.ArrayList; 61 import java.util.Arrays; 62 import java.util.Collections; 63 import java.util.LinkedList; 64 import java.util.List; 65 66 /** 67 * Tests for the {@link DisplayContent} class. 68 * 69 * Build/Install/Run: 70 * atest com.android.server.wm.DisplayContentTests 71 */ 72 @SmallTest 73 @Presubmit 74 @RunWith(AndroidJUnit4.class) 75 public class DisplayContentTests extends WindowTestsBase { 76 77 @Test 78 @FlakyTest(bugId = 77772044) 79 public void testForAllWindows() throws Exception { 80 final WindowState exitingAppWindow = createWindow(null, TYPE_BASE_APPLICATION, 81 mDisplayContent, "exiting app"); 82 final AppWindowToken exitingAppToken = exitingAppWindow.mAppToken; 83 exitingAppToken.mIsExiting = true; 84 exitingAppToken.getTask().mStack.mExitingAppTokens.add(exitingAppToken); 85 86 assertForAllWindowsOrder(Arrays.asList( 87 mWallpaperWindow, 88 exitingAppWindow, 89 mChildAppWindowBelow, 90 mAppWindow, 91 mChildAppWindowAbove, 92 mDockedDividerWindow, 93 mStatusBarWindow, 94 mNavBarWindow, 95 mImeWindow, 96 mImeDialogWindow)); 97 } 98 99 @Test 100 public void testForAllWindows_WithAppImeTarget() throws Exception { 101 final WindowState imeAppTarget = 102 createWindow(null, TYPE_BASE_APPLICATION, mDisplayContent, "imeAppTarget"); 103 104 sWm.mInputMethodTarget = imeAppTarget; 105 106 assertForAllWindowsOrder(Arrays.asList( 107 mWallpaperWindow, 108 mChildAppWindowBelow, 109 mAppWindow, 110 mChildAppWindowAbove, 111 imeAppTarget, 112 mImeWindow, 113 mImeDialogWindow, 114 mDockedDividerWindow, 115 mStatusBarWindow, 116 mNavBarWindow)); 117 } 118 119 @Test 120 public void testForAllWindows_WithChildWindowImeTarget() throws Exception { 121 sWm.mInputMethodTarget = mChildAppWindowAbove; 122 123 assertForAllWindowsOrder(Arrays.asList( 124 mWallpaperWindow, 125 mChildAppWindowBelow, 126 mAppWindow, 127 mChildAppWindowAbove, 128 mImeWindow, 129 mImeDialogWindow, 130 mDockedDividerWindow, 131 mStatusBarWindow, 132 mNavBarWindow)); 133 } 134 135 @Test 136 public void testForAllWindows_WithStatusBarImeTarget() throws Exception { 137 sWm.mInputMethodTarget = mStatusBarWindow; 138 139 assertForAllWindowsOrder(Arrays.asList( 140 mWallpaperWindow, 141 mChildAppWindowBelow, 142 mAppWindow, 143 mChildAppWindowAbove, 144 mDockedDividerWindow, 145 mStatusBarWindow, 146 mImeWindow, 147 mImeDialogWindow, 148 mNavBarWindow)); 149 } 150 151 @Test 152 public void testForAllWindows_WithInBetweenWindowToken() throws Exception { 153 // This window is set-up to be z-ordered between some windows that go in the same token like 154 // the nav bar and status bar. 155 final WindowState voiceInteractionWindow = createWindow(null, TYPE_VOICE_INTERACTION, 156 mDisplayContent, "voiceInteractionWindow"); 157 158 assertForAllWindowsOrder(Arrays.asList( 159 mWallpaperWindow, 160 mChildAppWindowBelow, 161 mAppWindow, 162 mChildAppWindowAbove, 163 mDockedDividerWindow, 164 voiceInteractionWindow, 165 mStatusBarWindow, 166 mNavBarWindow, 167 mImeWindow, 168 mImeDialogWindow)); 169 } 170 171 @Test 172 public void testComputeImeTarget() throws Exception { 173 // Verify that an app window can be an ime target. 174 final WindowState appWin = createWindow(null, TYPE_APPLICATION, mDisplayContent, "appWin"); 175 appWin.setHasSurface(true); 176 assertTrue(appWin.canBeImeTarget()); 177 WindowState imeTarget = mDisplayContent.computeImeTarget(false /* updateImeTarget */); 178 assertEquals(appWin, imeTarget); 179 appWin.mHidden = false; 180 181 // Verify that an child window can be an ime target. 182 final WindowState childWin = createWindow(appWin, 183 TYPE_APPLICATION_ATTACHED_DIALOG, "childWin"); 184 childWin.setHasSurface(true); 185 assertTrue(childWin.canBeImeTarget()); 186 imeTarget = mDisplayContent.computeImeTarget(false /* updateImeTarget */); 187 assertEquals(childWin, imeTarget); 188 } 189 190 /** 191 * This tests stack movement between displays and proper stack's, task's and app token's display 192 * container references updates. 193 */ 194 @Test 195 public void testMoveStackBetweenDisplays() throws Exception { 196 // Create a second display. 197 final DisplayContent dc = createNewDisplay(); 198 199 // Add stack with activity. 200 final TaskStack stack = createTaskStackOnDisplay(dc); 201 assertEquals(dc.getDisplayId(), stack.getDisplayContent().getDisplayId()); 202 assertEquals(dc, stack.getParent().getParent()); 203 assertEquals(dc, stack.getDisplayContent()); 204 205 final Task task = createTaskInStack(stack, 0 /* userId */); 206 final WindowTestUtils.TestAppWindowToken token = WindowTestUtils.createTestAppWindowToken( 207 dc); 208 task.addChild(token, 0); 209 assertEquals(dc, task.getDisplayContent()); 210 assertEquals(dc, token.getDisplayContent()); 211 212 // Move stack to first display. 213 mDisplayContent.moveStackToDisplay(stack, true /* onTop */); 214 assertEquals(mDisplayContent.getDisplayId(), stack.getDisplayContent().getDisplayId()); 215 assertEquals(mDisplayContent, stack.getParent().getParent()); 216 assertEquals(mDisplayContent, stack.getDisplayContent()); 217 assertEquals(mDisplayContent, task.getDisplayContent()); 218 assertEquals(mDisplayContent, token.getDisplayContent()); 219 } 220 221 /** 222 * This tests override configuration updates for display content. 223 */ 224 @Test 225 public void testDisplayOverrideConfigUpdate() throws Exception { 226 final int displayId = mDisplayContent.getDisplayId(); 227 final Configuration currentOverrideConfig = mDisplayContent.getOverrideConfiguration(); 228 229 // Create new, slightly changed override configuration and apply it to the display. 230 final Configuration newOverrideConfig = new Configuration(currentOverrideConfig); 231 newOverrideConfig.densityDpi += 120; 232 newOverrideConfig.fontScale += 0.3; 233 234 sWm.setNewDisplayOverrideConfiguration(newOverrideConfig, displayId); 235 236 // Check that override config is applied. 237 assertEquals(newOverrideConfig, mDisplayContent.getOverrideConfiguration()); 238 } 239 240 /** 241 * This tests global configuration updates when default display config is updated. 242 */ 243 @Test 244 public void testDefaultDisplayOverrideConfigUpdate() throws Exception { 245 final Configuration currentConfig = mDisplayContent.getConfiguration(); 246 247 // Create new, slightly changed override configuration and apply it to the display. 248 final Configuration newOverrideConfig = new Configuration(currentConfig); 249 newOverrideConfig.densityDpi += 120; 250 newOverrideConfig.fontScale += 0.3; 251 252 sWm.setNewDisplayOverrideConfiguration(newOverrideConfig, DEFAULT_DISPLAY); 253 254 // Check that global configuration is updated, as we've updated default display's config. 255 Configuration globalConfig = sWm.mRoot.getConfiguration(); 256 assertEquals(newOverrideConfig.densityDpi, globalConfig.densityDpi); 257 assertEquals(newOverrideConfig.fontScale, globalConfig.fontScale, 0.1 /* delta */); 258 259 // Return back to original values. 260 sWm.setNewDisplayOverrideConfiguration(currentConfig, DEFAULT_DISPLAY); 261 globalConfig = sWm.mRoot.getConfiguration(); 262 assertEquals(currentConfig.densityDpi, globalConfig.densityDpi); 263 assertEquals(currentConfig.fontScale, globalConfig.fontScale, 0.1 /* delta */); 264 } 265 266 /** 267 * Tests tapping on a stack in different display results in window gaining focus. 268 */ 269 @Test 270 public void testInputEventBringsCorrectDisplayInFocus() throws Exception { 271 DisplayContent dc0 = sWm.getDefaultDisplayContentLocked(); 272 // Create a second display 273 final DisplayContent dc1 = createNewDisplay(); 274 275 // Add stack with activity. 276 final TaskStack stack0 = createTaskStackOnDisplay(dc0); 277 final Task task0 = createTaskInStack(stack0, 0 /* userId */); 278 final WindowTestUtils.TestAppWindowToken token = 279 WindowTestUtils.createTestAppWindowToken(dc0); 280 task0.addChild(token, 0); 281 dc0.mTapDetector = new TaskTapPointerEventListener(sWm, dc0); 282 sWm.registerPointerEventListener(dc0.mTapDetector); 283 final TaskStack stack1 = createTaskStackOnDisplay(dc1); 284 final Task task1 = createTaskInStack(stack1, 0 /* userId */); 285 final WindowTestUtils.TestAppWindowToken token1 = 286 WindowTestUtils.createTestAppWindowToken(dc0); 287 task1.addChild(token1, 0); 288 dc1.mTapDetector = new TaskTapPointerEventListener(sWm, dc0); 289 sWm.registerPointerEventListener(dc1.mTapDetector); 290 291 // tap on primary display (by sending ACTION_DOWN followed by ACTION_UP) 292 DisplayMetrics dm0 = dc0.getDisplayMetrics(); 293 dc0.mTapDetector.onPointerEvent( 294 createTapEvent(dm0.widthPixels / 2, dm0.heightPixels / 2, true)); 295 dc0.mTapDetector.onPointerEvent( 296 createTapEvent(dm0.widthPixels / 2, dm0.heightPixels / 2, false)); 297 298 // Check focus is on primary display. 299 assertEquals(sWm.mCurrentFocus, dc0.findFocusedWindow()); 300 301 // Tap on secondary display 302 DisplayMetrics dm1 = dc1.getDisplayMetrics(); 303 dc1.mTapDetector.onPointerEvent( 304 createTapEvent(dm1.widthPixels / 2, dm1.heightPixels / 2, true)); 305 dc1.mTapDetector.onPointerEvent( 306 createTapEvent(dm1.widthPixels / 2, dm1.heightPixels / 2, false)); 307 308 // Check focus is on secondary. 309 assertEquals(sWm.mCurrentFocus, dc1.findFocusedWindow()); 310 } 311 312 @Test 313 public void testFocusedWindowMultipleDisplays() throws Exception { 314 // Create a focusable window and check that focus is calculated correctly 315 final WindowState window1 = 316 createWindow(null, TYPE_BASE_APPLICATION, mDisplayContent, "window1"); 317 assertEquals(window1, sWm.mRoot.computeFocusedWindow()); 318 319 // Check that a new display doesn't affect focus 320 final DisplayContent dc = createNewDisplay(); 321 assertEquals(window1, sWm.mRoot.computeFocusedWindow()); 322 323 // Add a window to the second display, and it should be focused 324 final WindowState window2 = createWindow(null, TYPE_BASE_APPLICATION, dc, "window2"); 325 assertEquals(window2, sWm.mRoot.computeFocusedWindow()); 326 327 // Move the first window to the to including parents, and make sure focus is updated 328 window1.getParent().positionChildAt(POSITION_TOP, window1, true); 329 assertEquals(window1, sWm.mRoot.computeFocusedWindow()); 330 } 331 332 @Test 333 public void testKeyguard_preventsSecondaryDisplayFocus() throws Exception { 334 final WindowState keyguard = createWindow(null, TYPE_STATUS_BAR, 335 sWm.getDefaultDisplayContentLocked(), "keyguard"); 336 assertEquals(keyguard, sWm.mRoot.computeFocusedWindow()); 337 338 // Add a window to a second display, and it should be focused 339 final DisplayContent dc = createNewDisplay(); 340 final WindowState win = createWindow(null, TYPE_BASE_APPLICATION, dc, "win"); 341 assertEquals(win, sWm.mRoot.computeFocusedWindow()); 342 343 mWmRule.getWindowManagerPolicy().keyguardShowingAndNotOccluded = true; 344 assertEquals(keyguard, sWm.mRoot.computeFocusedWindow()); 345 } 346 347 /** 348 * This tests setting the maximum ui width on a display. 349 */ 350 @Test 351 public void testMaxUiWidth() throws Exception { 352 final int baseWidth = 1440; 353 final int baseHeight = 2560; 354 final int baseDensity = 300; 355 356 mDisplayContent.updateBaseDisplayMetrics(baseWidth, baseHeight, baseDensity); 357 358 final int maxWidth = 300; 359 final int resultingHeight = (maxWidth * baseHeight) / baseWidth; 360 final int resultingDensity = (maxWidth * baseDensity) / baseWidth; 361 362 mDisplayContent.setMaxUiWidth(maxWidth); 363 verifySizes(mDisplayContent, maxWidth, resultingHeight, resultingDensity); 364 365 // Assert setting values again does not change; 366 mDisplayContent.updateBaseDisplayMetrics(baseWidth, baseHeight, baseDensity); 367 verifySizes(mDisplayContent, maxWidth, resultingHeight, resultingDensity); 368 369 final int smallerWidth = 200; 370 final int smallerHeight = 400; 371 final int smallerDensity = 100; 372 373 // Specify smaller dimension, verify that it is honored 374 mDisplayContent.updateBaseDisplayMetrics(smallerWidth, smallerHeight, smallerDensity); 375 verifySizes(mDisplayContent, smallerWidth, smallerHeight, smallerDensity); 376 377 // Verify that setting the max width to a greater value than the base width has no effect 378 mDisplayContent.setMaxUiWidth(maxWidth); 379 verifySizes(mDisplayContent, smallerWidth, smallerHeight, smallerDensity); 380 } 381 382 /** 383 * This test enforces that the pinned stack is always kept as the top stack. 384 */ 385 @Test 386 public void testPinnedStackLocation() { 387 final TaskStack pinnedStack = createStackControllerOnStackOnDisplay( 388 WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD, mDisplayContent).mContainer; 389 // Ensure that the pinned stack is the top stack 390 assertEquals(pinnedStack, mDisplayContent.getPinnedStack()); 391 assertEquals(pinnedStack, mDisplayContent.getTopStack()); 392 // By default, this should try to create a new stack on top 393 final TaskStack otherStack = createTaskStackOnDisplay(mDisplayContent); 394 // Ensure that the other stack is on the display. 395 assertEquals(mDisplayContent, otherStack.getDisplayContent()); 396 // Ensure that the pinned stack is still on top 397 assertEquals(pinnedStack, mDisplayContent.getTopStack()); 398 } 399 400 /** 401 * Test that WM does not report displays to AM that are pending to be removed. 402 */ 403 @Test 404 public void testDontReportDeferredRemoval() { 405 // Create a display and add an animating window to it. 406 final DisplayContent dc = createNewDisplay(); 407 final WindowState window = createWindow(null /* parent */, TYPE_BASE_APPLICATION, dc, "w"); 408 window.mAnimatingExit = true; 409 // Request display removal, it should be deferred. 410 dc.removeIfPossible(); 411 // Request ordered display ids from WM. 412 final SparseIntArray orderedDisplayIds = new SparseIntArray(); 413 sWm.getDisplaysInFocusOrder(orderedDisplayIds); 414 // Make sure that display that is marked for removal is not reported. 415 assertEquals(-1, orderedDisplayIds.indexOfValue(dc.getDisplayId())); 416 } 417 418 @Test 419 public void testDisplayCutout_rot0() throws Exception { 420 synchronized (sWm.getWindowManagerLock()) { 421 final DisplayContent dc = createNewDisplay(); 422 dc.mInitialDisplayWidth = 200; 423 dc.mInitialDisplayHeight = 400; 424 Rect r = new Rect(80, 0, 120, 10); 425 final DisplayCutout cutout = new WmDisplayCutout( 426 fromBoundingRect(r.left, r.top, r.right, r.bottom), null) 427 .computeSafeInsets(200, 400).getDisplayCutout(); 428 429 dc.mInitialDisplayCutout = cutout; 430 dc.setRotation(Surface.ROTATION_0); 431 dc.computeScreenConfiguration(new Configuration()); // recomputes dc.mDisplayInfo. 432 433 assertEquals(cutout, dc.getDisplayInfo().displayCutout); 434 } 435 } 436 437 @Test 438 public void testDisplayCutout_rot90() throws Exception { 439 synchronized (sWm.getWindowManagerLock()) { 440 final DisplayContent dc = createNewDisplay(); 441 dc.mInitialDisplayWidth = 200; 442 dc.mInitialDisplayHeight = 400; 443 Rect r1 = new Rect(80, 0, 120, 10); 444 final DisplayCutout cutout = new WmDisplayCutout( 445 fromBoundingRect(r1.left, r1.top, r1.right, r1.bottom), null) 446 .computeSafeInsets(200, 400).getDisplayCutout(); 447 448 dc.mInitialDisplayCutout = cutout; 449 dc.setRotation(Surface.ROTATION_90); 450 dc.computeScreenConfiguration(new Configuration()); // recomputes dc.mDisplayInfo. 451 452 final Rect r = new Rect(0, 80, 10, 120); 453 assertEquals(new WmDisplayCutout( 454 fromBoundingRect(r.left, r.top, r.right, r.bottom), null) 455 .computeSafeInsets(400, 200).getDisplayCutout(), dc.getDisplayInfo().displayCutout); 456 } 457 } 458 459 @Test 460 public void testLayoutSeq_assignedDuringLayout() throws Exception { 461 synchronized (sWm.getWindowManagerLock()) { 462 463 final DisplayContent dc = createNewDisplay(); 464 final WindowState win = createWindow(null /* parent */, TYPE_BASE_APPLICATION, dc, "w"); 465 466 dc.setLayoutNeeded(); 467 dc.performLayout(true /* initial */, false /* updateImeWindows */); 468 469 assertThat(win.mLayoutSeq, is(dc.mLayoutSeq)); 470 } 471 } 472 473 @Test 474 @SuppressLint("InlinedApi") 475 public void testOrientationDefinedByKeyguard() { 476 final DisplayContent dc = createNewDisplay(); 477 // Create a window that requests landscape orientation. It will define device orientation 478 // by default. 479 final WindowState window = createWindow(null /* parent */, TYPE_BASE_APPLICATION, dc, "w"); 480 window.mAppToken.setOrientation(SCREEN_ORIENTATION_LANDSCAPE); 481 482 final WindowState keyguard = createWindow(null, TYPE_STATUS_BAR, dc, "keyguard"); 483 keyguard.mHasSurface = true; 484 keyguard.mAttrs.screenOrientation = SCREEN_ORIENTATION_UNSPECIFIED; 485 486 assertEquals("Screen orientation must be defined by the app window by default", 487 SCREEN_ORIENTATION_LANDSCAPE, dc.getOrientation()); 488 489 keyguard.mAttrs.screenOrientation = SCREEN_ORIENTATION_PORTRAIT; 490 assertEquals("Visible keyguard must influence device orientation", 491 SCREEN_ORIENTATION_PORTRAIT, dc.getOrientation()); 492 493 sWm.setKeyguardGoingAway(true); 494 assertEquals("Keyguard that is going away must not influence device orientation", 495 SCREEN_ORIENTATION_LANDSCAPE, dc.getOrientation()); 496 } 497 498 @Test 499 public void testDisableDisplayInfoOverrideFromWindowManager() { 500 final DisplayContent dc = createNewDisplay(); 501 502 assertTrue(dc.mShouldOverrideDisplayConfiguration); 503 sWm.dontOverrideDisplayInfo(dc.getDisplayId()); 504 505 assertFalse(dc.mShouldOverrideDisplayConfiguration); 506 verify(sWm.mDisplayManagerInternal, times(1)) 507 .setDisplayInfoOverrideFromWindowManager(dc.getDisplayId(), null); 508 } 509 510 private static void verifySizes(DisplayContent displayContent, int expectedBaseWidth, 511 int expectedBaseHeight, int expectedBaseDensity) { 512 assertEquals(displayContent.mBaseDisplayWidth, expectedBaseWidth); 513 assertEquals(displayContent.mBaseDisplayHeight, expectedBaseHeight); 514 assertEquals(displayContent.mBaseDisplayDensity, expectedBaseDensity); 515 } 516 517 private void assertForAllWindowsOrder(List<WindowState> expectedWindowsBottomToTop) { 518 final LinkedList<WindowState> actualWindows = new LinkedList<>(); 519 520 // Test forward traversal. 521 mDisplayContent.forAllWindows(actualWindows::addLast, false /* traverseTopToBottom */); 522 assertThat("bottomToTop", actualWindows, is(expectedWindowsBottomToTop)); 523 524 actualWindows.clear(); 525 526 // Test backward traversal. 527 mDisplayContent.forAllWindows(actualWindows::addLast, true /* traverseTopToBottom */); 528 assertThat("topToBottom", actualWindows, is(reverseList(expectedWindowsBottomToTop))); 529 } 530 531 private static List<WindowState> reverseList(List<WindowState> list) { 532 final ArrayList<WindowState> result = new ArrayList<>(list); 533 Collections.reverse(result); 534 return result; 535 } 536 537 private MotionEvent createTapEvent(float x, float y, boolean isDownEvent) { 538 final long downTime = SystemClock.uptimeMillis(); 539 final long eventTime = SystemClock.uptimeMillis() + 100; 540 final int metaState = 0; 541 542 return MotionEvent.obtain( 543 downTime, 544 eventTime, 545 isDownEvent ? MotionEvent.ACTION_DOWN : MotionEvent.ACTION_UP, 546 x, 547 y, 548 metaState); 549 } 550 } 551