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 
23 import java.awt.Rectangle;
24 import java.lang.Integer;
25 import java.lang.String;
26 import java.util.ArrayList;
27 import java.util.Collections;
28 import java.util.HashMap;
29 import java.util.LinkedList;
30 import java.util.List;
31 
32 import java.util.Map;
33 import java.util.regex.Pattern;
34 import java.util.regex.Matcher;
35 
36 import static android.server.cts.ActivityManagerTestBase.HOME_STACK_ID;
37 import static android.server.cts.ActivityManagerTestBase.RECENTS_STACK_ID;
38 import static android.server.cts.StateLogger.log;
39 import static android.server.cts.StateLogger.logE;
40 
41 class ActivityManagerState {
42     public static final int DUMP_MODE_ACTIVITIES = 0;
43 
44     public static final String STATE_RESUMED = "RESUMED";
45     public static final String STATE_PAUSED = "PAUSED";
46     public static final String STATE_STOPPED = "STOPPED";
47 
48     public static final String RESIZE_MODE_RESIZEABLE = "RESIZE_MODE_RESIZEABLE";
49 
50     private static final String DUMPSYS_ACTIVITY_ACTIVITIES = "dumpsys activity activities";
51 
52     // Copied from ActivityRecord.java
53     private static final int APPLICATION_ACTIVITY_TYPE = 0;
54     private static final int HOME_ACTIVITY_TYPE = 1;
55     private static final int RECENTS_ACTIVITY_TYPE = 2;
56 
57     private final Pattern mDisplayIdPattern = Pattern.compile("Display #(\\d+).*");
58     private final Pattern mStackIdPattern = Pattern.compile("Stack #(\\d+)\\:");
59     private final Pattern mResumedActivityPattern =
60             Pattern.compile("ResumedActivity\\: ActivityRecord\\{(.+) u(\\d+) (\\S+) (\\S+)\\}");
61     private final Pattern mFocusedStackPattern =
62             Pattern.compile("mFocusedStack=ActivityStack\\{(.+) stackId=(\\d+), (.+)\\}(.+)");
63 
64     private final Pattern[] mExtractStackExitPatterns =
65             { mStackIdPattern, mResumedActivityPattern, mFocusedStackPattern, mDisplayIdPattern };
66 
67     // Stacks in z-order with the top most at the front of the list, starting with primary display.
68     private final List<ActivityStack> mStacks = new ArrayList();
69     // Stacks on all attached displays, in z-order with the top most at the front of the list.
70     private final Map<Integer, List<ActivityStack>> mDisplayStacks = new HashMap<>();
71     private KeyguardControllerState mKeyguardControllerState;
72     private int mFocusedStackId = -1;
73     private String mResumedActivityRecord = null;
74     private final List<String> mResumedActivities = new ArrayList();
75     private final LinkedList<String> mSysDump = new LinkedList();
76 
computeState(ITestDevice device)77     void computeState(ITestDevice device) throws DeviceNotAvailableException {
78         computeState(device, DUMP_MODE_ACTIVITIES);
79     }
80 
computeState(ITestDevice device, int dumpMode)81     void computeState(ITestDevice device, int dumpMode) throws DeviceNotAvailableException {
82         // It is possible the system is in the middle of transition to the right state when we get
83         // the dump. We try a few times to get the information we need before giving up.
84         int retriesLeft = 3;
85         boolean retry = false;
86         String dump = null;
87 
88         log("==============================");
89         log("     ActivityManagerState     ");
90         log("==============================");
91 
92         do {
93             if (retry) {
94                 log("***Incomplete AM state. Retrying...");
95                 // Wait half a second between retries for activity manager to finish transitioning.
96                 try {
97                     Thread.sleep(500);
98                 } catch (InterruptedException e) {
99                     log(e.toString());
100                     // Well I guess we are not waiting...
101                 }
102             }
103 
104             final CollectingOutputReceiver outputReceiver = new CollectingOutputReceiver();
105             String dumpsysCmd = "";
106             switch (dumpMode) {
107                 case DUMP_MODE_ACTIVITIES:
108                     dumpsysCmd = DUMPSYS_ACTIVITY_ACTIVITIES; break;
109             }
110             device.executeShellCommand(dumpsysCmd, outputReceiver);
111             dump = outputReceiver.getOutput();
112             parseSysDump(dump);
113 
114             retry = mStacks.isEmpty() || mFocusedStackId == -1 || (mResumedActivityRecord == null
115                     || mResumedActivities.isEmpty()) && !mKeyguardControllerState.keyguardShowing;
116         } while (retry && retriesLeft-- > 0);
117 
118         if (retry) {
119             log(dump);
120         }
121 
122         if (mStacks.isEmpty()) {
123             logE("No stacks found...");
124         }
125         if (mFocusedStackId == -1) {
126             logE("No focused stack found...");
127         }
128         if (mResumedActivityRecord == null) {
129             logE("No focused activity found...");
130         }
131         if (mResumedActivities.isEmpty()) {
132             logE("No resumed activities found...");
133         }
134     }
135 
parseSysDump(String sysDump)136     private void parseSysDump(String sysDump) {
137         reset();
138 
139         Collections.addAll(mSysDump, sysDump.split("\\n"));
140 
141         int currentDisplayId = 0;
142         while (!mSysDump.isEmpty()) {
143             final ActivityStack stack = ActivityStack.create(mSysDump, mStackIdPattern,
144                     mExtractStackExitPatterns, currentDisplayId);
145 
146             if (stack != null) {
147                 mStacks.add(stack);
148                 mDisplayStacks.get(currentDisplayId).add(stack);
149                 if (stack.mResumedActivity != null) {
150                     mResumedActivities.add(stack.mResumedActivity);
151                 }
152                 continue;
153             }
154 
155             KeyguardControllerState controller = KeyguardControllerState.create(
156                     mSysDump, new Pattern[0]);
157             if (controller != null) {
158                 mKeyguardControllerState = controller;
159                 continue;
160             }
161 
162             final String line = mSysDump.pop().trim();
163 
164             Matcher matcher = mFocusedStackPattern.matcher(line);
165             if (matcher.matches()) {
166                 log(line);
167                 final String stackId = matcher.group(2);
168                 log(stackId);
169                 mFocusedStackId = Integer.parseInt(stackId);
170                 continue;
171             }
172 
173             matcher = mResumedActivityPattern.matcher(line);
174             if (matcher.matches()) {
175                 log(line);
176                 mResumedActivityRecord = matcher.group(3);
177                 log(mResumedActivityRecord);
178                 continue;
179             }
180 
181             matcher = mDisplayIdPattern.matcher(line);
182             if (matcher.matches()) {
183                 log(line);
184                 final String displayId = matcher.group(1);
185                 log(displayId);
186                 currentDisplayId = Integer.parseInt(displayId);
187                 mDisplayStacks.put(currentDisplayId, new ArrayList<>());
188             }
189         }
190     }
191 
reset()192     private void reset() {
193         mStacks.clear();
194         mFocusedStackId = -1;
195         mResumedActivityRecord = null;
196         mResumedActivities.clear();
197         mSysDump.clear();
198         mKeyguardControllerState = null;
199     }
200 
getFrontStackId(int displayId)201     int getFrontStackId(int displayId) {
202         return mDisplayStacks.get(displayId).get(0).mStackId;
203     }
204 
getFocusedStackId()205     int getFocusedStackId() {
206         return mFocusedStackId;
207     }
208 
getFocusedActivity()209     String getFocusedActivity() {
210         return mResumedActivityRecord;
211     }
212 
getResumedActivity()213     String getResumedActivity() {
214         return mResumedActivities.get(0);
215     }
216 
getResumedActivitiesCount()217     int getResumedActivitiesCount() {
218         return mResumedActivities.size();
219     }
220 
getKeyguardControllerState()221     public KeyguardControllerState getKeyguardControllerState() {
222         return mKeyguardControllerState;
223     }
224 
containsStack(int stackId)225     boolean containsStack(int stackId) {
226         return getStackById(stackId) != null;
227     }
228 
getStackById(int stackId)229     ActivityStack getStackById(int stackId) {
230         for (ActivityStack stack : mStacks) {
231             if (stackId == stack.mStackId) {
232                 return stack;
233             }
234         }
235         return null;
236     }
237 
getStackPosition(int stackId)238     int getStackPosition(int stackId) {
239         for (int i = 0; i < mStacks.size(); i++) {
240             if (stackId == mStacks.get(i).mStackId) {
241                 return i;
242             }
243         }
244         return -1;
245     }
246 
getStacks()247     List<ActivityStack> getStacks() {
248         return new ArrayList(mStacks);
249     }
250 
getStackCount()251     int getStackCount() {
252         return mStacks.size();
253     }
254 
containsActivity(String activityName)255     boolean containsActivity(String activityName) {
256         for (ActivityStack stack : mStacks) {
257             for (ActivityTask task : stack.mTasks) {
258                 for (Activity activity : task.mActivities) {
259                     if (activity.name.equals(activityName)) {
260                         return true;
261                     }
262                 }
263             }
264         }
265         return false;
266     }
267 
isActivityVisible(String activityName)268     boolean isActivityVisible(String activityName) {
269         for (ActivityStack stack : mStacks) {
270             for (ActivityTask task : stack.mTasks) {
271                for (Activity activity : task.mActivities) {
272                    if (activity.name.equals(activityName)) {
273                        return activity.visible;
274                    }
275                }
276             }
277         }
278         return false;
279     }
280 
hasActivityState(String activityName, String activityState)281     boolean hasActivityState(String activityName, String activityState) {
282         String fullName = ActivityManagerTestBase.getActivityComponentName(activityName);
283         for (ActivityStack stack : mStacks) {
284             for (ActivityTask task : stack.mTasks) {
285                 for (Activity activity : task.mActivities) {
286                     if (activity.name.equals(fullName)) {
287                         return activity.state.equals(activityState);
288                     }
289                 }
290             }
291         }
292         return false;
293     }
294 
getActivityProcId(String activityName)295     int getActivityProcId(String activityName) {
296         for (ActivityStack stack : mStacks) {
297             for (ActivityTask task : stack.mTasks) {
298                for (Activity activity : task.mActivities) {
299                    if (activity.name.equals(activityName)) {
300                        return activity.procId;
301                    }
302                }
303             }
304         }
305         return -1;
306     }
307 
isHomeActivityVisible()308     boolean isHomeActivityVisible() {
309         final Activity homeActivity = getHomeActivity();
310         return homeActivity != null && homeActivity.visible;
311     }
312 
isRecentsActivityVisible()313     boolean isRecentsActivityVisible() {
314         final Activity recentsActivity = getRecentsActivity();
315         return recentsActivity != null && recentsActivity.visible;
316     }
317 
getHomeActivityName()318     String getHomeActivityName() {
319         Activity activity = getHomeActivity();
320         if (activity == null) {
321             return null;
322         }
323         return activity.name;
324     }
325 
getHomeTask()326     ActivityTask getHomeTask() {
327         ActivityStack homeStack = getStackById(HOME_STACK_ID);
328         if (homeStack != null) {
329             for (ActivityTask task : homeStack.mTasks) {
330                 if (task.mTaskType == HOME_ACTIVITY_TYPE) {
331                     return task;
332                 }
333             }
334             return null;
335         }
336         return null;
337     }
338 
getRecentsTask()339     ActivityTask getRecentsTask() {
340         ActivityStack recentsStack = getStackById(RECENTS_STACK_ID);
341         if (recentsStack != null) {
342             for (ActivityTask task : recentsStack.mTasks) {
343                 if (task.mTaskType == RECENTS_ACTIVITY_TYPE) {
344                     return task;
345                 }
346             }
347             return null;
348         }
349         return null;
350     }
351 
getHomeActivity()352     private Activity getHomeActivity() {
353         final ActivityTask homeTask = getHomeTask();
354         return homeTask != null ? homeTask.mActivities.get(homeTask.mActivities.size() - 1) : null;
355     }
356 
getRecentsActivity()357     private Activity getRecentsActivity() {
358         final ActivityTask recentsTask = getRecentsTask();
359         return recentsTask != null ? recentsTask.mActivities.get(recentsTask.mActivities.size() - 1)
360                 : null;
361     }
362 
getTaskByActivityName(String activityName)363     ActivityTask getTaskByActivityName(String activityName) {
364         return getTaskByActivityName(activityName, -1);
365     }
366 
getTaskByActivityName(String activityName, int stackId)367     ActivityTask getTaskByActivityName(String activityName, int stackId) {
368         String fullName = ActivityManagerTestBase.getActivityComponentName(activityName);
369         for (ActivityStack stack : mStacks) {
370             if (stackId == -1 || stackId == stack.mStackId) {
371                 for (ActivityTask task : stack.mTasks) {
372                     for (Activity activity : task.mActivities) {
373                         if (activity.name.equals(fullName)) {
374                             return task;
375                         }
376                     }
377                 }
378             }
379         }
380         return null;
381     }
382 
383     static class ActivityStack extends ActivityContainer {
384 
385         private static final Pattern TASK_ID_PATTERN = Pattern.compile("Task id #(\\d+)");
386         private static final Pattern RESUMED_ACTIVITY_PATTERN = Pattern.compile(
387                 "mResumedActivity\\: ActivityRecord\\{(.+) u(\\d+) (\\S+) (\\S+)\\}");
388 
389         int mDisplayId;
390         int mStackId;
391         String mResumedActivity;
392         ArrayList<ActivityTask> mTasks = new ArrayList();
393 
ActivityStack()394         private ActivityStack() {
395         }
396 
create(LinkedList<String> dump, Pattern stackIdPattern, Pattern[] exitPatterns, int displayId)397         static ActivityStack create(LinkedList<String> dump, Pattern stackIdPattern,
398                                     Pattern[] exitPatterns, int displayId) {
399             final String line = dump.peek().trim();
400 
401             final Matcher matcher = stackIdPattern.matcher(line);
402             if (!matcher.matches()) {
403                 // Not a stack.
404                 return null;
405             }
406             // For the stack Id line we just read.
407             dump.pop();
408 
409             final ActivityStack stack = new ActivityStack();
410             stack.mDisplayId = displayId;
411             log(line);
412             final String stackId = matcher.group(1);
413             log(stackId);
414             stack.mStackId = Integer.parseInt(stackId);
415             stack.extract(dump, exitPatterns);
416             return stack;
417         }
418 
extract(LinkedList<String> dump, Pattern[] exitPatterns)419         private void extract(LinkedList<String> dump, Pattern[] exitPatterns) {
420 
421             final List<Pattern> taskExitPatterns = new ArrayList();
422             Collections.addAll(taskExitPatterns, exitPatterns);
423             taskExitPatterns.add(TASK_ID_PATTERN);
424             taskExitPatterns.add(RESUMED_ACTIVITY_PATTERN);
425             final Pattern[] taskExitPatternsArray =
426                     taskExitPatterns.toArray(new Pattern[taskExitPatterns.size()]);
427 
428             while (!doneExtracting(dump, exitPatterns)) {
429                 final ActivityTask task =
430                         ActivityTask.create(dump, TASK_ID_PATTERN, taskExitPatternsArray);
431 
432                 if (task != null) {
433                     mTasks.add(task);
434                     continue;
435                 }
436 
437                 final String line = dump.pop().trim();
438 
439                 if (extractFullscreen(line)) {
440                     continue;
441                 }
442 
443                 if (extractBounds(line)) {
444                     continue;
445                 }
446 
447                 Matcher matcher = RESUMED_ACTIVITY_PATTERN.matcher(line);
448                 if (matcher.matches()) {
449                     log(line);
450                     mResumedActivity = matcher.group(3);
451                     log(mResumedActivity);
452                     continue;
453                 }
454             }
455         }
456 
457         /**
458          * @return the bottom task in the stack.
459          */
getBottomTask()460         ActivityTask getBottomTask() {
461             if (!mTasks.isEmpty()) {
462                 // NOTE: Unlike the ActivityManager internals, we dump the state from top to bottom,
463                 //       so the indices are inverted
464                 return mTasks.get(mTasks.size() - 1);
465             }
466             return null;
467         }
468 
469         /**
470          * @return the top task in the stack.
471          */
getTopTask()472         ActivityTask getTopTask() {
473             if (!mTasks.isEmpty()) {
474                 // NOTE: Unlike the ActivityManager internals, we dump the state from top to bottom,
475                 //       so the indices are inverted
476                 return mTasks.get(0);
477             }
478             return null;
479         }
480 
getTasks()481         List<ActivityTask> getTasks() {
482             return new ArrayList(mTasks);
483         }
484 
getTask(int taskId)485         ActivityTask getTask(int taskId) {
486             for (ActivityTask task : mTasks) {
487                 if (taskId == task.mTaskId) {
488                     return task;
489                 }
490             }
491             return null;
492         }
493     }
494 
495     static class ActivityTask extends ActivityContainer {
496         private static final Pattern TASK_RECORD_PATTERN = Pattern.compile("\\* TaskRecord\\"
497                 + "{(\\S+) #(\\d+) (\\S+)=(\\S+) U=(\\d+) StackId=(\\d+) sz=(\\d+)\\}");
498 
499         private static final Pattern LAST_NON_FULLSCREEN_BOUNDS_PATTERN = Pattern.compile(
500                 "mLastNonFullscreenBounds=Rect\\((\\d+), (\\d+) - (\\d+), (\\d+)\\)");
501 
502         private static final Pattern ORIG_ACTIVITY_PATTERN = Pattern.compile("origActivity=(\\S+)");
503         private static final Pattern REAL_ACTIVITY_PATTERN = Pattern.compile("realActivity=(\\S+)");
504 
505         private static final Pattern ACTIVITY_NAME_PATTERN = Pattern.compile(
506                 "\\* Hist #(\\d+)\\: ActivityRecord\\{(\\S+) u(\\d+) (\\S+) t(\\d+)\\}");
507 
508         private static final Pattern TASK_TYPE_PATTERN = Pattern.compile("autoRemoveRecents=(\\S+) "
509                 + "isPersistable=(\\S+) numFullscreen=(\\d+) taskType=(\\d+) "
510                 + "mTaskToReturnTo=(\\d+)");
511 
512         private static final Pattern RESIZABLE_PATTERN = Pattern.compile(
513                 ".*mResizeMode=([^\\s]+).*");
514 
515         int mTaskId;
516         int mStackId;
517         Rectangle mLastNonFullscreenBounds;
518         String mRealActivity;
519         String mOrigActivity;
520         ArrayList<Activity> mActivities = new ArrayList();
521         int mTaskType = -1;
522         int mReturnToType = -1;
523         private String mResizeMode;
524 
ActivityTask()525         private ActivityTask() {
526         }
527 
create( LinkedList<String> dump, Pattern taskIdPattern, Pattern[] exitPatterns)528         static ActivityTask create(
529                 LinkedList<String> dump, Pattern taskIdPattern, Pattern[] exitPatterns) {
530             final String line = dump.peek().trim();
531 
532             final Matcher matcher = taskIdPattern.matcher(line);
533             if (!matcher.matches()) {
534                 // Not a task.
535                 return null;
536             }
537             // For the task Id line we just read.
538             dump.pop();
539 
540             final ActivityTask task = new ActivityTask();
541             log(line);
542             final String taskId = matcher.group(1);
543             log(taskId);
544             task.mTaskId = Integer.parseInt(taskId);
545             task.extract(dump, exitPatterns);
546             return task;
547         }
548 
extract(LinkedList<String> dump, Pattern[] exitPatterns)549         private void extract(LinkedList<String> dump, Pattern[] exitPatterns) {
550             final List<Pattern> activityExitPatterns = new ArrayList();
551             Collections.addAll(activityExitPatterns, exitPatterns);
552             activityExitPatterns.add(ACTIVITY_NAME_PATTERN);
553             final Pattern[] activityExitPatternsArray =
554                     activityExitPatterns.toArray(new Pattern[activityExitPatterns.size()]);
555 
556             while (!doneExtracting(dump, exitPatterns)) {
557                 final Activity activity =
558                         Activity.create(dump, ACTIVITY_NAME_PATTERN, activityExitPatternsArray);
559 
560                 if (activity != null) {
561                     mActivities.add(activity);
562                     continue;
563                 }
564 
565                 final String line = dump.pop().trim();
566 
567                 if (extractFullscreen(line)) {
568                     continue;
569                 }
570 
571                 if (extractBounds(line)) {
572                     continue;
573                 }
574 
575                 if (extractMinimalSize(line)) {
576                     continue;
577                 }
578 
579                 Matcher matcher = TASK_RECORD_PATTERN.matcher(line);
580                 if (matcher.matches()) {
581                     log(line);
582                     final String stackId = matcher.group(6);
583                     mStackId = Integer.valueOf(stackId);
584                     log(stackId);
585                     continue;
586                 }
587 
588                 matcher = LAST_NON_FULLSCREEN_BOUNDS_PATTERN.matcher(line);
589                 if (matcher.matches()) {
590                     log(line);
591                     mLastNonFullscreenBounds = extractBounds(matcher);
592                 }
593 
594                 matcher = REAL_ACTIVITY_PATTERN.matcher(line);
595                 if (matcher.matches()) {
596                     if (mRealActivity == null) {
597                         log(line);
598                         mRealActivity = matcher.group(1);
599                         log(mRealActivity);
600                     }
601                     continue;
602                 }
603 
604                 matcher = ORIG_ACTIVITY_PATTERN.matcher(line);
605                 if (matcher.matches()) {
606                     if (mOrigActivity == null) {
607                         log(line);
608                         mOrigActivity = matcher.group(1);
609                         log(mOrigActivity);
610                     }
611                     continue;
612                 }
613 
614                 matcher = TASK_TYPE_PATTERN.matcher(line);
615                 if (matcher.matches()) {
616                     log(line);
617                     mTaskType = Integer.valueOf(matcher.group(4));
618                     mReturnToType = Integer.valueOf(matcher.group(5));
619                     continue;
620                 }
621 
622                 matcher = RESIZABLE_PATTERN.matcher(line);
623                 if (matcher.matches()) {
624                     log(line);
625                     mResizeMode = matcher.group(1);
626                     log(mResizeMode);
627                     continue;
628                 }
629             }
630         }
631 
getResizeMode()632         public String getResizeMode() {
633             return mResizeMode;
634         }
635 
636         /**
637          * @return whether this task contains the given activity.
638          */
containsActivity(String activityName)639         public boolean containsActivity(String activityName) {
640             for (Activity activity : mActivities) {
641                 if (activity.name.equals(activityName)) {
642                     return true;
643                 }
644             }
645             return false;
646         }
647     }
648 
649     static class Activity {
650         private static final Pattern STATE_PATTERN = Pattern.compile("state=(\\S+).*");
651         private static final Pattern VISIBILITY_PATTERN = Pattern.compile("keysPaused=(\\S+) "
652                 + "inHistory=(\\S+) visible=(\\S+) sleeping=(\\S+) idle=(\\S+) "
653                 + "mStartingWindowState=(\\S+)");
654         private static final Pattern FRONT_OF_TASK_PATTERN = Pattern.compile("frontOfTask=(\\S+) "
655                 + "task=TaskRecord\\{(\\S+) #(\\d+) A=(\\S+) U=(\\d+) StackId=(\\d+) sz=(\\d+)\\}");
656         private static final Pattern PROCESS_RECORD_PATTERN = Pattern.compile(
657                 "app=ProcessRecord\\{(\\S+) (\\d+):(\\S+)/(.+)\\}");
658 
659         String name;
660         String state;
661         boolean visible;
662         boolean frontOfTask;
663         int procId = -1;
664 
Activity()665         private Activity() {
666         }
667 
create( LinkedList<String> dump, Pattern activityNamePattern, Pattern[] exitPatterns)668         static Activity create(
669                 LinkedList<String> dump, Pattern activityNamePattern, Pattern[] exitPatterns) {
670             final String line = dump.peek().trim();
671 
672             final Matcher matcher = activityNamePattern.matcher(line);
673             if (!matcher.matches()) {
674                 // Not an activity.
675                 return null;
676             }
677             // For the activity name line we just read.
678             dump.pop();
679 
680             final Activity activity = new Activity();
681             log(line);
682             activity.name = matcher.group(4);
683             log(activity.name);
684             activity.extract(dump, exitPatterns);
685             return activity;
686         }
687 
extract(LinkedList<String> dump, Pattern[] exitPatterns)688         private void extract(LinkedList<String> dump, Pattern[] exitPatterns) {
689 
690             while (!doneExtracting(dump, exitPatterns)) {
691                 final String line = dump.pop().trim();
692 
693                 // Break the activity extraction once we hit an empty line
694                 if (line.isEmpty()) {
695                     break;
696                 }
697 
698                 Matcher matcher = VISIBILITY_PATTERN.matcher(line);
699                 if (matcher.matches()) {
700                     log(line);
701                     final String visibleString = matcher.group(3);
702                     visible = Boolean.valueOf(visibleString);
703                     log(visibleString);
704                     continue;
705                 }
706 
707                 matcher = STATE_PATTERN.matcher(line);
708                 if (matcher.matches()) {
709                     log(line);
710                     state = matcher.group(1);
711                     log(state);
712                     continue;
713                 }
714 
715                 matcher = PROCESS_RECORD_PATTERN.matcher(line);
716                 if (matcher.matches()) {
717                     log(line);
718                     final String procIdString = matcher.group(2);
719                     procId = Integer.valueOf(procIdString);
720                     log(procIdString);
721                     continue;
722                 }
723 
724                 matcher = FRONT_OF_TASK_PATTERN.matcher(line);
725                 if (matcher.matches()) {
726                     log(line);
727                     final String frontOfTaskString = matcher.group(1);
728                     frontOfTask = Boolean.valueOf(frontOfTaskString);
729                     log(frontOfTaskString);
730                     continue;
731                 }
732             }
733         }
734     }
735 
736     static abstract class ActivityContainer {
737         protected static final Pattern FULLSCREEN_PATTERN = Pattern.compile("mFullscreen=(\\S+)");
738         protected static final Pattern BOUNDS_PATTERN =
739                 Pattern.compile("mBounds=Rect\\((\\d+), (\\d+) - (\\d+), (\\d+)\\)");
740         protected static final Pattern MIN_WIDTH_PATTERN =
741                 Pattern.compile("mMinWidth=(\\d+)");
742         protected static final Pattern MIN_HEIGHT_PATTERN =
743                 Pattern.compile("mMinHeight=(\\d+)");
744 
745         protected boolean mFullscreen;
746         protected Rectangle mBounds;
747         protected int mMinWidth = -1;
748         protected int mMinHeight = -1;
749 
extractFullscreen(String line)750         boolean extractFullscreen(String line) {
751             final Matcher matcher = FULLSCREEN_PATTERN.matcher(line);
752             if (!matcher.matches()) {
753                 return false;
754             }
755             log(line);
756             final String fullscreen = matcher.group(1);
757             log(fullscreen);
758             mFullscreen = Boolean.valueOf(fullscreen);
759             return true;
760         }
761 
extractBounds(String line)762         boolean extractBounds(String line) {
763             final Matcher matcher = BOUNDS_PATTERN.matcher(line);
764             if (!matcher.matches()) {
765                 return false;
766             }
767             log(line);
768             mBounds = extractBounds(matcher);
769             return true;
770         }
771 
extractBounds(Matcher matcher)772         static Rectangle extractBounds(Matcher matcher) {
773             final int left = Integer.valueOf(matcher.group(1));
774             final int top = Integer.valueOf(matcher.group(2));
775             final int right = Integer.valueOf(matcher.group(3));
776             final int bottom = Integer.valueOf(matcher.group(4));
777             final Rectangle rect = new Rectangle(left, top, right - left, bottom - top);
778 
779             log(rect.toString());
780             return rect;
781         }
782 
extractMinimalSize(String line)783         boolean extractMinimalSize(String line) {
784             final Matcher minWidthMatcher = MIN_WIDTH_PATTERN.matcher(line);
785             final Matcher minHeightMatcher = MIN_HEIGHT_PATTERN.matcher(line);
786 
787             if (minWidthMatcher.matches()) {
788                 log(line);
789                 mMinWidth = Integer.valueOf(minWidthMatcher.group(1));
790             } else if (minHeightMatcher.matches()) {
791                 log(line);
792                 mMinHeight = Integer.valueOf(minHeightMatcher.group(1));
793             } else {
794                 return false;
795             }
796             return true;
797         }
798 
getBounds()799         Rectangle getBounds() {
800             return mBounds;
801         }
802 
isFullscreen()803         boolean isFullscreen() {
804             return mFullscreen;
805         }
806 
getMinWidth()807         int getMinWidth() {
808             return mMinWidth;
809         }
810 
getMinHeight()811         int getMinHeight() {
812             return mMinHeight;
813         }
814     }
815 
816     static class KeyguardControllerState {
817         private static final Pattern NAME_PATTERN = Pattern.compile("KeyguardController:");
818         private static final Pattern SHOWING_PATTERN = Pattern.compile("mKeyguardShowing=(\\S+)");
819         private static final Pattern OCCLUDED_PATTERN = Pattern.compile("mOccluded=(\\S+)");
820 
821         boolean keyguardShowing;
822         boolean keyguardOccluded;
823 
KeyguardControllerState()824         private KeyguardControllerState() {
825         }
826 
create(LinkedList<String> dump, Pattern[] exitPatterns)827         static KeyguardControllerState create(LinkedList<String> dump, Pattern[] exitPatterns) {
828             final String line = dump.peek().trim();
829 
830             final Matcher matcher = NAME_PATTERN.matcher(line);
831             if (!matcher.matches()) {
832                 // Not KeyguardController
833                 return null;
834             }
835 
836             // For the KeyguardController line we just read.
837             dump.pop();
838 
839             final KeyguardControllerState controller = new KeyguardControllerState();
840             controller.extract(dump, exitPatterns);
841             return controller;
842         }
843 
extract(LinkedList<String> dump, Pattern[] exitPatterns)844         private void extract(LinkedList<String> dump, Pattern[] exitPatterns) {
845 
846             while (!doneExtracting(dump, exitPatterns)) {
847                 final String line = dump.pop().trim();
848 
849                 Matcher matcher = SHOWING_PATTERN.matcher(line);
850                 if (matcher.matches()) {
851                     log(line);
852                     final String showingString = matcher.group(1);
853                     keyguardShowing = Boolean.valueOf(showingString);
854                     log(showingString);
855                     continue;
856                 }
857 
858                 matcher = OCCLUDED_PATTERN.matcher(line);
859                 if (matcher.matches()) {
860                     log(line);
861                     final String occludedString = matcher.group(1);
862                     keyguardOccluded = Boolean.valueOf(occludedString);
863                     log(occludedString);
864                     continue;
865                 }
866             }
867         }
868     }
869 
doneExtracting(LinkedList<String> dump, Pattern[] exitPatterns)870     static boolean doneExtracting(LinkedList<String> dump, Pattern[] exitPatterns) {
871         if (dump.isEmpty()) {
872             return true;
873         }
874         final String line = dump.peek().trim();
875 
876         for (Pattern pattern : exitPatterns) {
877             if (pattern.matcher(line).matches()) {
878                 return true;
879             }
880         }
881         return false;
882     }
883 }
884