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