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