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 17package android.server.cts; 18 19import com.android.tradefed.device.CollectingOutputReceiver; 20import com.android.tradefed.device.DeviceNotAvailableException; 21import com.android.tradefed.device.ITestDevice; 22 23import java.awt.Rectangle; 24import java.lang.Integer; 25import java.lang.String; 26import java.util.ArrayList; 27import java.util.Collections; 28import java.util.LinkedList; 29import java.util.List; 30 31import java.util.regex.Pattern; 32import java.util.regex.Matcher; 33 34import static android.server.cts.ActivityManagerTestBase.HOME_STACK_ID; 35import static android.server.cts.StateLogger.log; 36import static android.server.cts.StateLogger.logE; 37 38class ActivityManagerState { 39 private static final String DUMPSYS_ACTIVITY_ACTIVITIES = "dumpsys activity activities"; 40 41 // Copied from ActivityRecord.java 42 private static final int APPLICATION_ACTIVITY_TYPE = 0; 43 private static final int HOME_ACTIVITY_TYPE = 1; 44 private static final int RECENTS_ACTIVITY_TYPE = 2; 45 46 private final Pattern mDisplayIdPattern = Pattern.compile("Display #(\\d+)"); 47 private final Pattern mStackIdPattern = Pattern.compile("Stack #(\\d+)\\:"); 48 private final Pattern mFocusedActivityPattern = 49 Pattern.compile("mFocusedActivity\\: ActivityRecord\\{(.+) u(\\d+) (\\S+) (\\S+)\\}"); 50 private final Pattern mFocusedStackPattern = 51 Pattern.compile("mFocusedStack=ActivityStack\\{(.+) stackId=(\\d+), (.+)\\}(.+)"); 52 53 private final Pattern[] mExtractStackExitPatterns = 54 { mStackIdPattern, mFocusedActivityPattern, mFocusedStackPattern}; 55 56 // Stacks in z-order with the top most at the front of the list. 57 private final List<ActivityStack> mStacks = new ArrayList(); 58 private int mFocusedStackId = -1; 59 private String mFocusedActivityRecord = null; 60 private final List<String> mResumedActivities = new ArrayList(); 61 private final LinkedList<String> mSysDump = new LinkedList(); 62 63 void computeState(ITestDevice device) throws DeviceNotAvailableException { 64 // It is possible the system is in the middle of transition to the right state when we get 65 // the dump. We try a few times to get the information we need before giving up. 66 int retriesLeft = 3; 67 boolean retry = false; 68 String dump = null; 69 70 log("=============================="); 71 log(" ActivityManagerState "); 72 log("=============================="); 73 74 do { 75 if (retry) { 76 log("***Incomplete AM state. Retrying..."); 77 // Wait half a second between retries for activity manager to finish transitioning. 78 try { 79 Thread.sleep(500); 80 } catch (InterruptedException e) { 81 log(e.toString()); 82 // Well I guess we are not waiting... 83 } 84 } 85 86 final CollectingOutputReceiver outputReceiver = new CollectingOutputReceiver(); 87 device.executeShellCommand(DUMPSYS_ACTIVITY_ACTIVITIES, outputReceiver); 88 dump = outputReceiver.getOutput(); 89 parseSysDump(dump); 90 91 retry = mStacks.isEmpty() || mFocusedStackId == -1 || mFocusedActivityRecord == null 92 || mResumedActivities.isEmpty(); 93 } while (retry && retriesLeft-- > 0); 94 95 if (retry) { 96 log(dump); 97 } 98 99 if (mStacks.isEmpty()) { 100 logE("No stacks found..."); 101 } 102 if (mFocusedStackId == -1) { 103 logE("No focused stack found..."); 104 } 105 if (mFocusedActivityRecord == null) { 106 logE("No focused activity found..."); 107 } 108 if (mResumedActivities.isEmpty()) { 109 logE("No resumed activities found..."); 110 } 111 } 112 113 private void parseSysDump(String sysDump) { 114 reset(); 115 116 Collections.addAll(mSysDump, sysDump.split("\\n")); 117 118 int currentDisplayId = 0; 119 while (!mSysDump.isEmpty()) { 120 final ActivityStack stack = ActivityStack.create(mSysDump, mStackIdPattern, 121 mExtractStackExitPatterns, currentDisplayId); 122 123 if (stack != null) { 124 mStacks.add(stack); 125 if (stack.mResumedActivity != null) { 126 mResumedActivities.add(stack.mResumedActivity); 127 } 128 continue; 129 } 130 131 final String line = mSysDump.pop().trim(); 132 133 Matcher matcher = mFocusedStackPattern.matcher(line); 134 if (matcher.matches()) { 135 log(line); 136 final String stackId = matcher.group(2); 137 log(stackId); 138 mFocusedStackId = Integer.parseInt(stackId); 139 continue; 140 } 141 142 matcher = mFocusedActivityPattern.matcher(line); 143 if (matcher.matches()) { 144 log(line); 145 mFocusedActivityRecord = matcher.group(3); 146 log(mFocusedActivityRecord); 147 continue; 148 } 149 150 matcher = mDisplayIdPattern.matcher(line); 151 if (matcher.matches()) { 152 log(line); 153 final String displayId = matcher.group(2); 154 log(displayId); 155 currentDisplayId = Integer.parseInt(displayId); 156 } 157 } 158 } 159 160 private void reset() { 161 mStacks.clear(); 162 mFocusedStackId = -1; 163 mFocusedActivityRecord = null; 164 mResumedActivities.clear(); 165 mSysDump.clear(); 166 } 167 168 int getFrontStackId() { 169 return mStacks.get(0).mStackId; 170 } 171 172 int getFocusedStackId() { 173 return mFocusedStackId; 174 } 175 176 String getFocusedActivity() { 177 return mFocusedActivityRecord; 178 } 179 180 String getResumedActivity() { 181 return mResumedActivities.get(0); 182 } 183 184 int getResumedActivitiesCount() { 185 return mResumedActivities.size(); 186 } 187 188 boolean containsStack(int stackId) { 189 return getStackById(stackId) != null; 190 } 191 192 ActivityStack getStackById(int stackId) { 193 for (ActivityStack stack : mStacks) { 194 if (stackId == stack.mStackId) { 195 return stack; 196 } 197 } 198 return null; 199 } 200 201 List<ActivityStack> getStacks() { 202 return new ArrayList(mStacks); 203 } 204 205 int getStackCount() { 206 return mStacks.size(); 207 } 208 209 boolean isActivityVisible(String activityName) { 210 for (ActivityStack stack : mStacks) { 211 for (ActivityTask task : stack.mTasks) { 212 for (Activity activity : task.mActivities) { 213 if (activity.name.equals(activityName)) { 214 return activity.visible; 215 } 216 } 217 } 218 } 219 return false; 220 } 221 222 boolean isHomeActivityVisible() { 223 final Activity homeActivity = getHomeActivity(); 224 return homeActivity != null && homeActivity.visible; 225 } 226 227 private Activity getHomeActivity() { 228 for (ActivityStack stack : mStacks) { 229 if (stack.mStackId != HOME_STACK_ID) { 230 continue; 231 } 232 233 for (ActivityTask task : stack.mTasks) { 234 if (task.mTaskType != HOME_ACTIVITY_TYPE) { 235 continue; 236 } 237 return task.mActivities.get(task.mActivities.size() - 1); 238 } 239 240 return null; 241 } 242 return null; 243 } 244 245 ActivityTask getTaskByActivityName(String activityName) { 246 return getTaskByActivityName(activityName, -1); 247 } 248 249 ActivityTask getTaskByActivityName(String activityName, int stackId) { 250 String fullName = ActivityManagerTestBase.getActivityComponentName(activityName); 251 for (ActivityStack stack : mStacks) { 252 if (stackId == -1 || stackId == stack.mStackId) { 253 for (ActivityTask task : stack.mTasks) { 254 for (Activity activity : task.mActivities) { 255 if (activity.name.equals(fullName)) { 256 return task; 257 } 258 } 259 } 260 } 261 } 262 return null; 263 } 264 265 static class ActivityStack extends ActivityContainer { 266 267 private static final Pattern TASK_ID_PATTERN = Pattern.compile("Task id #(\\d+)"); 268 private static final Pattern RESUMED_ACTIVITY_PATTERN = Pattern.compile( 269 "mResumedActivity\\: ActivityRecord\\{(.+) u(\\d+) (\\S+) (\\S+)\\}"); 270 271 int mDisplayId; 272 int mStackId; 273 String mResumedActivity; 274 ArrayList<ActivityTask> mTasks = new ArrayList(); 275 276 private ActivityStack() { 277 } 278 279 static ActivityStack create(LinkedList<String> dump, Pattern stackIdPattern, 280 Pattern[] exitPatterns, int displayId) { 281 final String line = dump.peek().trim(); 282 283 final Matcher matcher = stackIdPattern.matcher(line); 284 if (!matcher.matches()) { 285 // Not a stack. 286 return null; 287 } 288 // For the stack Id line we just read. 289 dump.pop(); 290 291 final ActivityStack stack = new ActivityStack(); 292 stack.mDisplayId = displayId; 293 log(line); 294 final String stackId = matcher.group(1); 295 log(stackId); 296 stack.mStackId = Integer.parseInt(stackId); 297 stack.extract(dump, exitPatterns); 298 return stack; 299 } 300 301 private void extract(LinkedList<String> dump, Pattern[] exitPatterns) { 302 303 final List<Pattern> taskExitPatterns = new ArrayList(); 304 Collections.addAll(taskExitPatterns, exitPatterns); 305 taskExitPatterns.add(TASK_ID_PATTERN); 306 taskExitPatterns.add(RESUMED_ACTIVITY_PATTERN); 307 final Pattern[] taskExitPatternsArray = 308 taskExitPatterns.toArray(new Pattern[taskExitPatterns.size()]); 309 310 while (!doneExtracting(dump, exitPatterns)) { 311 final ActivityTask task = 312 ActivityTask.create(dump, TASK_ID_PATTERN, taskExitPatternsArray); 313 314 if (task != null) { 315 mTasks.add(task); 316 continue; 317 } 318 319 final String line = dump.pop().trim(); 320 321 if (extractFullscreen(line)) { 322 continue; 323 } 324 325 if (extractBounds(line)) { 326 continue; 327 } 328 329 Matcher matcher = RESUMED_ACTIVITY_PATTERN.matcher(line); 330 if (matcher.matches()) { 331 log(line); 332 mResumedActivity = matcher.group(3); 333 log(mResumedActivity); 334 continue; 335 } 336 } 337 } 338 339 List<ActivityTask> getTasks() { 340 return new ArrayList(mTasks); 341 } 342 343 ActivityTask getTask(int taskId) { 344 for (ActivityTask task : mTasks) { 345 if (taskId == task.mTaskId) { 346 return task; 347 } 348 } 349 return null; 350 } 351 } 352 353 static class ActivityTask extends ActivityContainer { 354 private static final Pattern TASK_RECORD_PATTERN = Pattern.compile("\\* TaskRecord\\" 355 + "{(\\S+) #(\\d+) (\\S+)=(\\S+) U=(\\d+) StackId=(\\d+) sz=(\\d+)\\}"); 356 357 private static final Pattern LAST_NON_FULLSCREEN_BOUNDS_PATTERN = Pattern.compile( 358 "mLastNonFullscreenBounds=Rect\\((\\d+), (\\d+) - (\\d+), (\\d+)\\)"); 359 360 private static final Pattern ORIG_ACTIVITY_PATTERN = Pattern.compile("origActivity=(\\S+)"); 361 private static final Pattern REAL_ACTIVITY_PATTERN = Pattern.compile("realActivity=(\\S+)"); 362 363 private static final Pattern ACTIVITY_NAME_PATTERN = Pattern.compile( 364 "\\* Hist #(\\d+)\\: ActivityRecord\\{(\\S+) u(\\d+) (\\S+) t(\\d+)\\}"); 365 366 private static final Pattern TASK_TYPE_PATTERN = Pattern.compile("autoRemoveRecents=(\\S+) " 367 + "isPersistable=(\\S+) numFullscreen=(\\d+) taskType=(\\d+) " 368 + "mTaskToReturnTo=(\\d+)"); 369 370 int mTaskId; 371 int mStackId; 372 Rectangle mLastNonFullscreenBounds; 373 String mRealActivity; 374 String mOrigActivity; 375 ArrayList<Activity> mActivities = new ArrayList(); 376 int mTaskType = -1; 377 int mReturnToType = -1; 378 379 private ActivityTask() { 380 } 381 382 static ActivityTask create( 383 LinkedList<String> dump, Pattern taskIdPattern, Pattern[] exitPatterns) { 384 final String line = dump.peek().trim(); 385 386 final Matcher matcher = taskIdPattern.matcher(line); 387 if (!matcher.matches()) { 388 // Not a task. 389 return null; 390 } 391 // For the task Id line we just read. 392 dump.pop(); 393 394 final ActivityTask task = new ActivityTask(); 395 log(line); 396 final String taskId = matcher.group(1); 397 log(taskId); 398 task.mTaskId = Integer.parseInt(taskId); 399 task.extract(dump, exitPatterns); 400 return task; 401 } 402 403 private void extract(LinkedList<String> dump, Pattern[] exitPatterns) { 404 final List<Pattern> activityExitPatterns = new ArrayList(); 405 Collections.addAll(activityExitPatterns, exitPatterns); 406 activityExitPatterns.add(ACTIVITY_NAME_PATTERN); 407 final Pattern[] activityExitPatternsArray = 408 activityExitPatterns.toArray(new Pattern[activityExitPatterns.size()]); 409 410 while (!doneExtracting(dump, exitPatterns)) { 411 final Activity activity = 412 Activity.create(dump, ACTIVITY_NAME_PATTERN, activityExitPatternsArray); 413 414 if (activity != null) { 415 mActivities.add(activity); 416 continue; 417 } 418 419 final String line = dump.pop().trim(); 420 421 if (extractFullscreen(line)) { 422 continue; 423 } 424 425 if (extractBounds(line)) { 426 continue; 427 } 428 429 if (extractMinimalSize(line)) { 430 continue; 431 } 432 433 Matcher matcher = TASK_RECORD_PATTERN.matcher(line); 434 if (matcher.matches()) { 435 log(line); 436 final String stackId = matcher.group(6); 437 mStackId = Integer.valueOf(stackId); 438 log(stackId); 439 continue; 440 } 441 442 matcher = LAST_NON_FULLSCREEN_BOUNDS_PATTERN.matcher(line); 443 if (matcher.matches()) { 444 log(line); 445 mLastNonFullscreenBounds = extractBounds(matcher); 446 } 447 448 matcher = REAL_ACTIVITY_PATTERN.matcher(line); 449 if (matcher.matches()) { 450 if (mRealActivity == null) { 451 log(line); 452 mRealActivity = matcher.group(1); 453 log(mRealActivity); 454 } 455 continue; 456 } 457 458 matcher = ORIG_ACTIVITY_PATTERN.matcher(line); 459 if (matcher.matches()) { 460 if (mOrigActivity == null) { 461 log(line); 462 mOrigActivity = matcher.group(1); 463 log(mOrigActivity); 464 } 465 continue; 466 } 467 468 matcher = TASK_TYPE_PATTERN.matcher(line); 469 if (matcher.matches()) { 470 log(line); 471 mTaskType = Integer.valueOf(matcher.group(4)); 472 mReturnToType = Integer.valueOf(matcher.group(5)); 473 continue; 474 } 475 } 476 } 477 } 478 479 static class Activity { 480 private static final Pattern VISIBILITY_PATTERN = Pattern.compile("keysPaused=(\\S+) " 481 + "inHistory=(\\S+) visible=(\\S+) sleeping=(\\S+) idle=(\\S+) " 482 + "mStartingWindowState=(\\S+)"); 483 private static final Pattern FRONT_OF_TASK_PATTERN = Pattern.compile("frontOfTask=(\\S+) " 484 + "task=TaskRecord\\{(\\S+) #(\\d+) A=(\\S+) U=(\\d+) StackId=(\\d+) sz=(\\d+)\\}"); 485 486 String name; 487 boolean visible; 488 boolean frontOfTask; 489 490 private Activity() { 491 } 492 493 static Activity create( 494 LinkedList<String> dump, Pattern activityNamePattern, Pattern[] exitPatterns) { 495 final String line = dump.peek().trim(); 496 497 final Matcher matcher = activityNamePattern.matcher(line); 498 if (!matcher.matches()) { 499 // Not an activity. 500 return null; 501 } 502 // For the activity name line we just read. 503 dump.pop(); 504 505 final Activity activity = new Activity(); 506 log(line); 507 activity.name = matcher.group(4); 508 log(activity.name); 509 activity.extract(dump, exitPatterns); 510 return activity; 511 } 512 513 private void extract(LinkedList<String> dump, Pattern[] exitPatterns) { 514 515 while (!doneExtracting(dump, exitPatterns)) { 516 final String line = dump.pop().trim(); 517 518 Matcher matcher = VISIBILITY_PATTERN.matcher(line); 519 if (matcher.matches()) { 520 log(line); 521 final String visibleString = matcher.group(3); 522 visible = Boolean.valueOf(visibleString); 523 log(visibleString); 524 continue; 525 } 526 527 matcher = FRONT_OF_TASK_PATTERN.matcher(line); 528 if (matcher.matches()) { 529 log(line); 530 final String frontOfTaskString = matcher.group(1); 531 frontOfTask = Boolean.valueOf(frontOfTaskString); 532 log(frontOfTaskString); 533 continue; 534 } 535 } 536 } 537 } 538 539 static abstract class ActivityContainer { 540 protected static final Pattern FULLSCREEN_PATTERN = Pattern.compile("mFullscreen=(\\S+)"); 541 protected static final Pattern BOUNDS_PATTERN = 542 Pattern.compile("mBounds=Rect\\((\\d+), (\\d+) - (\\d+), (\\d+)\\)"); 543 protected static final Pattern MIN_WIDTH_PATTERN = 544 Pattern.compile("mMinWidth=(\\d+)"); 545 protected static final Pattern MIN_HEIGHT_PATTERN = 546 Pattern.compile("mMinHeight=(\\d+)"); 547 548 protected boolean mFullscreen; 549 protected Rectangle mBounds; 550 protected int mMinWidth = -1; 551 protected int mMinHeight = -1; 552 553 boolean extractFullscreen(String line) { 554 final Matcher matcher = FULLSCREEN_PATTERN.matcher(line); 555 if (!matcher.matches()) { 556 return false; 557 } 558 log(line); 559 final String fullscreen = matcher.group(1); 560 log(fullscreen); 561 mFullscreen = Boolean.valueOf(fullscreen); 562 return true; 563 } 564 565 boolean extractBounds(String line) { 566 final Matcher matcher = BOUNDS_PATTERN.matcher(line); 567 if (!matcher.matches()) { 568 return false; 569 } 570 log(line); 571 mBounds = extractBounds(matcher); 572 return true; 573 } 574 575 static Rectangle extractBounds(Matcher matcher) { 576 final int left = Integer.valueOf(matcher.group(1)); 577 final int top = Integer.valueOf(matcher.group(2)); 578 final int right = Integer.valueOf(matcher.group(3)); 579 final int bottom = Integer.valueOf(matcher.group(4)); 580 final Rectangle rect = new Rectangle(left, top, right - left, bottom - top); 581 582 log(rect.toString()); 583 return rect; 584 } 585 586 boolean extractMinimalSize(String line) { 587 final Matcher minWidthMatcher = MIN_WIDTH_PATTERN.matcher(line); 588 final Matcher minHeightMatcher = MIN_HEIGHT_PATTERN.matcher(line); 589 590 if (minWidthMatcher.matches()) { 591 log(line); 592 mMinWidth = Integer.valueOf(minWidthMatcher.group(1)); 593 } else if (minHeightMatcher.matches()) { 594 log(line); 595 mMinHeight = Integer.valueOf(minHeightMatcher.group(1)); 596 } else { 597 return false; 598 } 599 return true; 600 } 601 602 Rectangle getBounds() { 603 return mBounds; 604 } 605 606 boolean isFullscreen() { 607 return mFullscreen; 608 } 609 610 int getMinWidth() { 611 return mMinWidth; 612 } 613 614 int getMinHeight() { 615 return mMinHeight; 616 } 617 } 618 619 static boolean doneExtracting(LinkedList<String> dump, Pattern[] exitPatterns) { 620 if (dump.isEmpty()) { 621 return true; 622 } 623 final String line = dump.peek().trim(); 624 625 for (Pattern pattern : exitPatterns) { 626 if (pattern.matcher(line).matches()) { 627 return true; 628 } 629 } 630 return false; 631 } 632} 633