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 android.server.cts; 18 19 import com.android.tradefed.device.CollectingOutputReceiver; 20 import com.android.tradefed.device.DeviceNotAvailableException; 21 import com.android.tradefed.device.ITestDevice; 22 import com.android.tradefed.log.LogUtil.CLog; 23 24 import java.awt.Rectangle; 25 import java.lang.String; 26 import java.util.ArrayList; 27 import java.util.Collections; 28 import java.util.LinkedList; 29 import java.util.List; 30 31 import java.util.regex.Pattern; 32 import java.util.regex.Matcher; 33 34 import static android.server.cts.StateLogger.log; 35 import static android.server.cts.StateLogger.logE; 36 37 class WindowManagerState { 38 private static final String DUMPSYS_WINDOWS_APPS = "dumpsys window apps"; 39 private static final String DUMPSYS_WINDOWS_VISIBLE_APPS = "dumpsys window visible-apps"; 40 41 private static final Pattern sWindowPattern = 42 Pattern.compile("Window #(\\d+) Window\\{([0-9a-fA-F]+) u(\\d+) (.+)\\}\\:"); 43 private static final Pattern sStartingWindowPattern = 44 Pattern.compile("Window #(\\d+) Window\\{([0-9a-fA-F]+) u(\\d+) Starting (.+)\\}\\:"); 45 private static final Pattern sExitingWindowPattern = 46 Pattern.compile("Window #(\\d+) Window\\{([0-9a-fA-F]+) u(\\d+) (.+) EXITING\\}\\:"); 47 48 private static final Pattern sFocusedWindowPattern = Pattern.compile( 49 "mCurrentFocus=Window\\{([0-9a-fA-F]+) u(\\d+) (\\S+)\\}"); 50 private static final Pattern sAppErrorFocusedWindowPattern = Pattern.compile( 51 "mCurrentFocus=Window\\{([0-9a-fA-F]+) u(\\d+) Application Error\\: (\\S+)\\}"); 52 53 private static final Pattern sFocusedAppPattern = 54 Pattern.compile("mFocusedApp=AppWindowToken\\{(.+) token=Token\\{(.+) " 55 + "ActivityRecord\\{(.+) u(\\d+) (\\S+) (\\S+)"); 56 57 private static final Pattern sStackIdPattern = Pattern.compile("mStackId=(\\d+)"); 58 59 private static final Pattern[] sExtractStackExitPatterns = { 60 sStackIdPattern, sWindowPattern, sStartingWindowPattern, sExitingWindowPattern, 61 sFocusedWindowPattern, sAppErrorFocusedWindowPattern, sFocusedAppPattern }; 62 63 // Windows in z-order with the top most at the front of the list. 64 private List<String> mWindows = new ArrayList(); 65 private List<WindowState> mWindowStates = new ArrayList(); 66 private List<WindowStack> mStacks = new ArrayList(); 67 private List<Display> mDisplays = new ArrayList(); 68 private String mFocusedWindow = null; 69 private String mFocusedApp = null; 70 private final LinkedList<String> mSysDump = new LinkedList(); 71 computeState(ITestDevice device, boolean visibleOnly)72 void computeState(ITestDevice device, boolean visibleOnly) throws DeviceNotAvailableException { 73 // It is possible the system is in the middle of transition to the right state when we get 74 // the dump. We try a few times to get the information we need before giving up. 75 int retriesLeft = 3; 76 boolean retry = false; 77 String dump = null; 78 79 log("=============================="); 80 log(" WindowManagerState "); 81 log("=============================="); 82 do { 83 if (retry) { 84 log("***Incomplete WM state. Retrying..."); 85 // Wait half a second between retries for window manager to finish transitioning... 86 try { 87 Thread.sleep(500); 88 } catch (InterruptedException e) { 89 log(e.toString()); 90 // Well I guess we are not waiting... 91 } 92 } 93 94 final CollectingOutputReceiver outputReceiver = new CollectingOutputReceiver(); 95 final String dumpsysCmd = visibleOnly ? 96 DUMPSYS_WINDOWS_VISIBLE_APPS : DUMPSYS_WINDOWS_APPS; 97 device.executeShellCommand(dumpsysCmd, outputReceiver); 98 dump = outputReceiver.getOutput(); 99 parseSysDump(dump, visibleOnly); 100 101 retry = mWindows.isEmpty() || mFocusedWindow == null || mFocusedApp == null; 102 } while (retry && retriesLeft-- > 0); 103 104 if (retry) { 105 log(dump); 106 } 107 108 if (mWindows.isEmpty()) { 109 logE("No Windows found..."); 110 } 111 if (mFocusedWindow == null) { 112 logE("No Focused Window..."); 113 } 114 if (mFocusedApp == null) { 115 logE("No Focused App..."); 116 } 117 } 118 parseSysDump(String sysDump, boolean visibleOnly)119 private void parseSysDump(String sysDump, boolean visibleOnly) { 120 reset(); 121 122 Collections.addAll(mSysDump, sysDump.split("\\n")); 123 124 while (!mSysDump.isEmpty()) { 125 final Display display = 126 Display.create(mSysDump, sExtractStackExitPatterns); 127 if (display != null) { 128 log(display.toString()); 129 mDisplays.add(display); 130 continue; 131 } 132 133 final WindowStack stack = 134 WindowStack.create(mSysDump, sStackIdPattern, sExtractStackExitPatterns); 135 136 if (stack != null) { 137 mStacks.add(stack); 138 continue; 139 } 140 141 142 final WindowState ws = WindowState.create(mSysDump, sExtractStackExitPatterns); 143 if (ws != null) { 144 log(ws.toString()); 145 146 if (visibleOnly) { 147 // Check to see if we are in the middle of transitioning. If we are, we want to 148 // skip dumping until window manager is done transitioning windows. 149 if (ws.isStartingWindow()) { 150 log("Skipping dump due to starting window transition..."); 151 return; 152 } 153 154 if (ws.isExitingWindow()) { 155 log("Skipping dump due to exiting window transition..."); 156 return; 157 } 158 } 159 160 mWindows.add(ws.getName()); 161 mWindowStates.add(ws); 162 continue; 163 } 164 165 final String line = mSysDump.pop().trim(); 166 167 Matcher matcher = sFocusedWindowPattern.matcher(line); 168 if (matcher.matches()) { 169 log(line); 170 final String focusedWindow = matcher.group(3); 171 log(focusedWindow); 172 mFocusedWindow = focusedWindow; 173 continue; 174 } 175 176 matcher = sAppErrorFocusedWindowPattern.matcher(line); 177 if (matcher.matches()) { 178 log(line); 179 final String focusedWindow = matcher.group(3); 180 log(focusedWindow); 181 mFocusedWindow = focusedWindow; 182 continue; 183 } 184 185 matcher = sFocusedAppPattern.matcher(line); 186 if (matcher.matches()) { 187 log(line); 188 final String focusedApp = matcher.group(5); 189 log(focusedApp); 190 mFocusedApp = focusedApp; 191 continue; 192 } 193 } 194 } 195 getMatchingWindowTokens(final String windowName, List<String> tokenList)196 void getMatchingWindowTokens(final String windowName, List<String> tokenList) { 197 tokenList.clear(); 198 199 for (WindowState ws : mWindowStates) { 200 if (windowName.equals(ws.getName())) { 201 tokenList.add(ws.getToken()); 202 } 203 } 204 } 205 getMatchingWindowState(final String windowName, List<WindowState> windowList)206 void getMatchingWindowState(final String windowName, List<WindowState> windowList) { 207 windowList.clear(); 208 for (WindowState ws : mWindowStates) { 209 if (windowName.equals(ws.getName())) { 210 windowList.add(ws); 211 } 212 } 213 } 214 getDisplay(int displayId)215 Display getDisplay(int displayId) { 216 for (Display display : mDisplays) { 217 if (displayId == display.getDisplayId()) { 218 return display; 219 } 220 } 221 return null; 222 } 223 getFrontWindow()224 String getFrontWindow() { 225 if (mWindows == null || mWindows.isEmpty()) { 226 return null; 227 } 228 return mWindows.get(0); 229 } 230 getFocusedWindow()231 String getFocusedWindow() { 232 return mFocusedWindow; 233 } 234 getFocusedApp()235 String getFocusedApp() { 236 return mFocusedApp; 237 } 238 getFrontStackId()239 int getFrontStackId() { 240 return mStacks.get(0).mStackId; 241 } 242 containsStack(int stackId)243 boolean containsStack(int stackId) { 244 for (WindowStack stack : mStacks) { 245 if (stackId == stack.mStackId) { 246 return true; 247 } 248 } 249 return false; 250 } 251 isWindowVisible(String windowName)252 boolean isWindowVisible(String windowName) { 253 for (String window : mWindows) { 254 if (window.equals(windowName)) { 255 return true; 256 } 257 } 258 return false; 259 } 260 getStack(int stackId)261 WindowStack getStack(int stackId) { 262 for (WindowStack stack : mStacks) { 263 if (stackId == stack.mStackId) { 264 return stack; 265 } 266 } 267 return null; 268 } 269 reset()270 private void reset() { 271 mSysDump.clear(); 272 mStacks.clear(); 273 mDisplays.clear(); 274 mWindows.clear(); 275 mWindowStates.clear(); 276 mFocusedWindow = null; 277 mFocusedApp = null; 278 } 279 280 static class WindowStack extends WindowContainer { 281 282 private static final Pattern sTaskIdPattern = Pattern.compile("taskId=(\\d+)"); 283 284 int mStackId; 285 ArrayList<WindowTask> mTasks = new ArrayList(); 286 WindowStack()287 private WindowStack() { 288 289 } 290 create( LinkedList<String> dump, Pattern stackIdPattern, Pattern[] exitPatterns)291 static WindowStack create( 292 LinkedList<String> dump, Pattern stackIdPattern, Pattern[] exitPatterns) { 293 final String line = dump.peek().trim(); 294 295 final Matcher matcher = stackIdPattern.matcher(line); 296 if (!matcher.matches()) { 297 // Not a stack. 298 return null; 299 } 300 // For the stack Id line we just read. 301 dump.pop(); 302 303 final WindowStack stack = new WindowStack(); 304 log(line); 305 final String stackId = matcher.group(1); 306 log(stackId); 307 stack.mStackId = Integer.parseInt(stackId); 308 stack.extract(dump, exitPatterns); 309 return stack; 310 } 311 extract(LinkedList<String> dump, Pattern[] exitPatterns)312 void extract(LinkedList<String> dump, Pattern[] exitPatterns) { 313 314 final List<Pattern> taskExitPatterns = new ArrayList(); 315 Collections.addAll(taskExitPatterns, exitPatterns); 316 taskExitPatterns.add(sTaskIdPattern); 317 final Pattern[] taskExitPatternsArray = 318 taskExitPatterns.toArray(new Pattern[taskExitPatterns.size()]); 319 320 while (!doneExtracting(dump, exitPatterns)) { 321 final WindowTask task = 322 WindowTask.create(dump, sTaskIdPattern, taskExitPatternsArray); 323 324 if (task != null) { 325 mTasks.add(task); 326 continue; 327 } 328 329 final String line = dump.pop().trim(); 330 331 if (extractFullscreen(line)) { 332 continue; 333 } 334 335 if (extractBounds(line)) { 336 continue; 337 } 338 } 339 } 340 getTask(int taskId)341 WindowTask getTask(int taskId) { 342 for (WindowTask task : mTasks) { 343 if (taskId == task.mTaskId) { 344 return task; 345 } 346 } 347 return null; 348 } 349 } 350 351 static class WindowTask extends WindowContainer { 352 private static final Pattern sTempInsetBoundsPattern = 353 Pattern.compile("mTempInsetBounds=\\[(\\d+),(\\d+)\\]\\[(\\d+),(\\d+)\\]"); 354 355 private static final Pattern sAppTokenPattern = Pattern.compile( 356 "Activity #(\\d+) AppWindowToken\\{(\\S+) token=Token\\{(\\S+) " 357 + "ActivityRecord\\{(\\S+) u(\\d+) (\\S+) t(\\d+)\\}\\}\\}"); 358 359 360 int mTaskId; 361 Rectangle mTempInsetBounds; 362 List<String> mAppTokens = new ArrayList(); 363 WindowTask()364 private WindowTask() { 365 } 366 create( LinkedList<String> dump, Pattern taskIdPattern, Pattern[] exitPatterns)367 static WindowTask create( 368 LinkedList<String> dump, Pattern taskIdPattern, Pattern[] exitPatterns) { 369 final String line = dump.peek().trim(); 370 371 final Matcher matcher = taskIdPattern.matcher(line); 372 if (!matcher.matches()) { 373 // Not a task. 374 return null; 375 } 376 // For the task Id line we just read. 377 dump.pop(); 378 379 final WindowTask task = new WindowTask(); 380 log(line); 381 final String taskId = matcher.group(1); 382 log(taskId); 383 task.mTaskId = Integer.parseInt(taskId); 384 task.extract(dump, exitPatterns); 385 return task; 386 } 387 extract(LinkedList<String> dump, Pattern[] exitPatterns)388 private void extract(LinkedList<String> dump, Pattern[] exitPatterns) { 389 while (!doneExtracting(dump, exitPatterns)) { 390 final String line = dump.pop().trim(); 391 392 if (extractFullscreen(line)) { 393 continue; 394 } 395 396 if (extractBounds(line)) { 397 continue; 398 } 399 400 Matcher matcher = sTempInsetBoundsPattern.matcher(line); 401 if (matcher.matches()) { 402 log(line); 403 mTempInsetBounds = extractBounds(matcher); 404 } 405 406 matcher = sAppTokenPattern.matcher(line); 407 if (matcher.matches()) { 408 log(line); 409 final String appToken = matcher.group(6); 410 log(appToken); 411 mAppTokens.add(appToken); 412 continue; 413 } 414 } 415 } 416 } 417 418 static abstract class WindowContainer { 419 protected static final Pattern sFullscreenPattern = Pattern.compile("mFullscreen=(\\S+)"); 420 protected static final Pattern sBoundsPattern = 421 Pattern.compile("mBounds=\\[(-?\\d+),(-?\\d+)\\]\\[(-?\\d+),(-?\\d+)\\]"); 422 423 protected boolean mFullscreen; 424 protected Rectangle mBounds; 425 doneExtracting(LinkedList<String> dump, Pattern[] exitPatterns)426 static boolean doneExtracting(LinkedList<String> dump, Pattern[] exitPatterns) { 427 if (dump.isEmpty()) { 428 return true; 429 } 430 final String line = dump.peek().trim(); 431 432 for (Pattern pattern : exitPatterns) { 433 if (pattern.matcher(line).matches()) { 434 return true; 435 } 436 } 437 return false; 438 } 439 extractFullscreen(String line)440 boolean extractFullscreen(String line) { 441 final Matcher matcher = sFullscreenPattern.matcher(line); 442 if (!matcher.matches()) { 443 return false; 444 } 445 log(line); 446 final String fullscreen = matcher.group(1); 447 log(fullscreen); 448 mFullscreen = Boolean.valueOf(fullscreen); 449 return true; 450 } 451 extractBounds(String line)452 boolean extractBounds(String line) { 453 final Matcher matcher = sBoundsPattern.matcher(line); 454 if (!matcher.matches()) { 455 return false; 456 } 457 log(line); 458 mBounds = extractBounds(matcher); 459 return true; 460 } 461 extractBounds(Matcher matcher)462 static Rectangle extractBounds(Matcher matcher) { 463 final int left = Integer.valueOf(matcher.group(1)); 464 final int top = Integer.valueOf(matcher.group(2)); 465 final int right = Integer.valueOf(matcher.group(3)); 466 final int bottom = Integer.valueOf(matcher.group(4)); 467 final Rectangle rect = new Rectangle(left, top, right - left, bottom - top); 468 469 log(rect.toString()); 470 return rect; 471 } 472 extractMultipleBounds(Matcher matcher, int groupIndex, Rectangle... rectList)473 static void extractMultipleBounds(Matcher matcher, int groupIndex, Rectangle... rectList) { 474 for (Rectangle rect : rectList) { 475 if (rect == null) { 476 return; 477 } 478 final int left = Integer.valueOf(matcher.group(groupIndex++)); 479 final int top = Integer.valueOf(matcher.group(groupIndex++)); 480 final int right = Integer.valueOf(matcher.group(groupIndex++)); 481 final int bottom = Integer.valueOf(matcher.group(groupIndex++)); 482 rect.setBounds(left, top, right - left, bottom - top); 483 } 484 } 485 getBounds()486 Rectangle getBounds() { 487 return mBounds; 488 } 489 isFullscreen()490 boolean isFullscreen() { 491 return mFullscreen; 492 } 493 } 494 495 static class Display extends WindowContainer { 496 private static final String TAG = "[Display] "; 497 498 private static final Pattern sDisplayIdPattern = 499 Pattern.compile("Display: mDisplayId=(\\d+)"); 500 private static final Pattern sDisplayInfoPattern = 501 Pattern.compile("(.+) (\\d+)dpi cur=(\\d+)x(\\d+) app=(\\d+)x(\\d+) (.+)"); 502 503 private final int mDisplayId; 504 private Rectangle mDisplayRect = new Rectangle(); 505 private Rectangle mAppRect = new Rectangle(); 506 private int mDpi; 507 Display(int displayId)508 private Display(int displayId) { 509 mDisplayId = displayId; 510 } 511 getDisplayId()512 int getDisplayId() { 513 return mDisplayId; 514 } 515 getDpi()516 int getDpi() { 517 return mDpi; 518 } 519 getDisplayRect()520 Rectangle getDisplayRect() { 521 return mDisplayRect; 522 } 523 getAppRect()524 Rectangle getAppRect() { 525 return mAppRect; 526 } 527 create(LinkedList<String> dump, Pattern[] exitPatterns)528 static Display create(LinkedList<String> dump, Pattern[] exitPatterns) { 529 // TODO: exit pattern for displays? 530 final String line = dump.peek().trim(); 531 532 Matcher matcher = sDisplayIdPattern.matcher(line); 533 if (!matcher.matches()) { 534 return null; 535 } 536 537 log(TAG + "DISPLAY_ID: " + line); 538 dump.pop(); 539 540 final int displayId = Integer.valueOf(matcher.group(1)); 541 final Display display = new Display(displayId); 542 display.extract(dump, exitPatterns); 543 return display; 544 } 545 extract(LinkedList<String> dump, Pattern[] exitPatterns)546 private void extract(LinkedList<String> dump, Pattern[] exitPatterns) { 547 while (!doneExtracting(dump, exitPatterns)) { 548 final String line = dump.pop().trim(); 549 550 final Matcher matcher = sDisplayInfoPattern.matcher(line); 551 if (matcher.matches()) { 552 log(TAG + "DISPLAY_INFO: " + line); 553 mDpi = Integer.valueOf(matcher.group(2)); 554 555 final int displayWidth = Integer.valueOf(matcher.group(3)); 556 final int displayHeight = Integer.valueOf(matcher.group(4)); 557 mDisplayRect.setBounds(0, 0, displayWidth, displayHeight); 558 559 final int appWidth = Integer.valueOf(matcher.group(5)); 560 final int appHeight = Integer.valueOf(matcher.group(6)); 561 mAppRect.setBounds(0, 0, appWidth, appHeight); 562 563 // break as we don't need other info for now 564 break; 565 } 566 // Extract other info here if needed 567 } 568 } 569 570 @Override toString()571 public String toString() { 572 return "Display #" + mDisplayId + ": mDisplayRect=" + mDisplayRect 573 + " mAppRect=" + mAppRect; 574 } 575 } 576 577 static class WindowState extends WindowContainer { 578 private static final String TAG = "[WindowState] "; 579 580 private static final String RECT_STR = "\\[(\\d+),(\\d+)\\]\\[(\\d+),(\\d+)\\]"; 581 private static final Pattern sFramePattern = 582 Pattern.compile("Frames: containing=" + RECT_STR + " parent=" + RECT_STR); 583 private static final Pattern sWindowAssociationPattern = 584 Pattern.compile("mDisplayId=(\\d+) stackId=(\\d+) (.+)"); 585 586 private final String mName; 587 private final String mAppToken; 588 private final boolean mStarting; 589 private final boolean mExiting; 590 private int mDisplayId; 591 private int mStackId; 592 private Rectangle mContainingFrame = new Rectangle(); 593 private Rectangle mParentFrame = new Rectangle(); 594 WindowState(Matcher matcher, boolean starting, boolean exiting)595 private WindowState(Matcher matcher, boolean starting, boolean exiting) { 596 mName = matcher.group(4); 597 mAppToken = matcher.group(2); 598 mStarting = starting; 599 mExiting = exiting; 600 } 601 getName()602 String getName() { 603 return mName; 604 } 605 getToken()606 String getToken() { 607 return mAppToken; 608 } 609 isStartingWindow()610 boolean isStartingWindow() { 611 return mStarting; 612 } 613 isExitingWindow()614 boolean isExitingWindow() { 615 return mExiting; 616 } 617 getDisplayId()618 int getDisplayId() { 619 return mDisplayId; 620 } 621 getStackId()622 int getStackId() { 623 return mStackId; 624 } 625 getContainingFrame()626 Rectangle getContainingFrame() { 627 return mContainingFrame; 628 } 629 getParentFrame()630 Rectangle getParentFrame() { 631 return mParentFrame; 632 } 633 create(LinkedList<String> dump, Pattern[] exitPatterns)634 static WindowState create(LinkedList<String> dump, Pattern[] exitPatterns) { 635 final String line = dump.peek().trim(); 636 637 Matcher matcher = sWindowPattern.matcher(line); 638 if (!matcher.matches()) { 639 return null; 640 } 641 642 log(TAG + "WINDOW: " + line); 643 dump.pop(); 644 645 final WindowState window; 646 Matcher specialMatcher = sStartingWindowPattern.matcher(line); 647 if (specialMatcher.matches()) { 648 log(TAG + "STARTING: " + line); 649 window = new WindowState(specialMatcher, true, false); 650 } else { 651 specialMatcher = sExitingWindowPattern.matcher(line); 652 if (specialMatcher.matches()) { 653 log(TAG + "EXITING: " + line); 654 window = new WindowState(specialMatcher, false, true); 655 } else { 656 window = new WindowState(matcher, false, false); 657 } 658 } 659 660 window.extract(dump, exitPatterns); 661 return window; 662 } 663 extract(LinkedList<String> dump, Pattern[] exitPatterns)664 private void extract(LinkedList<String> dump, Pattern[] exitPatterns) { 665 while (!doneExtracting(dump, exitPatterns)) { 666 final String line = dump.pop().trim(); 667 668 Matcher matcher = sWindowAssociationPattern.matcher(line); 669 if (matcher.matches()) { 670 log(TAG + "WINDOW_ASSOCIATION: " + line); 671 mDisplayId = Integer.valueOf(matcher.group(1)); 672 mStackId = Integer.valueOf(matcher.group(2)); 673 continue; 674 } 675 676 matcher = sFramePattern.matcher(line); 677 if (matcher.matches()) { 678 log(TAG + "FRAME: " + line); 679 extractMultipleBounds(matcher, 1, mContainingFrame, mParentFrame); 680 continue; 681 } 682 683 // Extract other info here if needed 684 } 685 } 686 687 @Override toString()688 public String toString() { 689 return "WindowState: {" + mAppToken + " " + mName 690 + (mStarting ? " STARTING" : "") + (mExiting ? " EXITING" : "") + "}" 691 + " cf=" + mContainingFrame + " pf=" + mParentFrame; 692 } 693 } 694 } 695