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