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.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.ActivityManagerTestBase.HOME_STACK_ID;
35 import static android.server.cts.StateLogger.log;
36 import static android.server.cts.StateLogger.logE;
37 
38 class 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 
computeState(ITestDevice device)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 
parseSysDump(String sysDump)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 
reset()160     private void reset() {
161         mStacks.clear();
162         mFocusedStackId = -1;
163         mFocusedActivityRecord = null;
164         mResumedActivities.clear();
165         mSysDump.clear();
166     }
167 
getFrontStackId()168     int getFrontStackId() {
169         return mStacks.get(0).mStackId;
170     }
171 
getFocusedStackId()172     int getFocusedStackId() {
173         return mFocusedStackId;
174     }
175 
getFocusedActivity()176     String getFocusedActivity() {
177         return mFocusedActivityRecord;
178     }
179 
getResumedActivity()180     String getResumedActivity() {
181         return mResumedActivities.get(0);
182     }
183 
getResumedActivitiesCount()184     int getResumedActivitiesCount() {
185         return mResumedActivities.size();
186     }
187 
containsStack(int stackId)188     boolean containsStack(int stackId) {
189         return getStackById(stackId) != null;
190     }
191 
getStackById(int stackId)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 
getStacks()201     List<ActivityStack> getStacks() {
202         return new ArrayList(mStacks);
203     }
204 
getStackCount()205     int getStackCount() {
206         return mStacks.size();
207     }
208 
isActivityVisible(String activityName)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 
isHomeActivityVisible()222     boolean isHomeActivityVisible() {
223         final Activity homeActivity = getHomeActivity();
224         return homeActivity != null && homeActivity.visible;
225     }
226 
getHomeActivity()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 
getTaskByActivityName(String activityName)245     ActivityTask getTaskByActivityName(String activityName) {
246         return getTaskByActivityName(activityName, -1);
247     }
248 
getTaskByActivityName(String activityName, int stackId)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 
ActivityStack()276         private ActivityStack() {
277         }
278 
create(LinkedList<String> dump, Pattern stackIdPattern, Pattern[] exitPatterns, int displayId)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 
extract(LinkedList<String> dump, Pattern[] exitPatterns)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 
getTasks()339         List<ActivityTask> getTasks() {
340             return new ArrayList(mTasks);
341         }
342 
getTask(int taskId)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 
ActivityTask()379         private ActivityTask() {
380         }
381 
create( LinkedList<String> dump, Pattern taskIdPattern, Pattern[] exitPatterns)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 
extract(LinkedList<String> dump, Pattern[] exitPatterns)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 
Activity()490         private Activity() {
491         }
492 
create( LinkedList<String> dump, Pattern activityNamePattern, Pattern[] exitPatterns)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 
extract(LinkedList<String> dump, Pattern[] exitPatterns)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 
extractFullscreen(String line)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 
extractBounds(String line)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 
extractBounds(Matcher matcher)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 
extractMinimalSize(String line)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 
getBounds()602         Rectangle getBounds() {
603             return mBounds;
604         }
605 
isFullscreen()606         boolean isFullscreen() {
607             return mFullscreen;
608         }
609 
getMinWidth()610         int getMinWidth() {
611             return mMinWidth;
612         }
613 
getMinHeight()614         int getMinHeight() {
615             return mMinHeight;
616         }
617     }
618 
doneExtracting(LinkedList<String> dump, Pattern[] exitPatterns)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