Home | History | Annotate | Download | only in cts
      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 
     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 
    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 
    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 
    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 
    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 
    224     String getFrontWindow() {
    225         if (mWindows == null || mWindows.isEmpty()) {
    226             return null;
    227         }
    228         return mWindows.get(0);
    229     }
    230 
    231     String getFocusedWindow() {
    232         return mFocusedWindow;
    233     }
    234 
    235     String getFocusedApp() {
    236         return mFocusedApp;
    237     }
    238 
    239     int getFrontStackId() {
    240         return mStacks.get(0).mStackId;
    241     }
    242 
    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 
    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 
    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 
    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 
    287         private WindowStack() {
    288 
    289         }
    290 
    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 
    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 
    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 
    364         private WindowTask() {
    365         }
    366 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    486         Rectangle getBounds() {
    487             return mBounds;
    488         }
    489 
    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 
    508         private Display(int displayId) {
    509             mDisplayId = displayId;
    510         }
    511 
    512         int getDisplayId() {
    513             return mDisplayId;
    514         }
    515 
    516         int getDpi() {
    517             return mDpi;
    518         }
    519 
    520         Rectangle getDisplayRect() {
    521             return mDisplayRect;
    522         }
    523 
    524         Rectangle getAppRect() {
    525             return mAppRect;
    526         }
    527 
    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 
    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
    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 
    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 
    602         String getName() {
    603             return mName;
    604         }
    605 
    606         String getToken() {
    607             return mAppToken;
    608         }
    609 
    610         boolean isStartingWindow() {
    611             return mStarting;
    612         }
    613 
    614         boolean isExitingWindow() {
    615             return mExiting;
    616         }
    617 
    618         int getDisplayId() {
    619             return mDisplayId;
    620         }
    621 
    622         int getStackId() {
    623             return mStackId;
    624         }
    625 
    626         Rectangle getContainingFrame() {
    627             return mContainingFrame;
    628         }
    629 
    630         Rectangle getParentFrame() {
    631             return mParentFrame;
    632         }
    633 
    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 
    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
    688         public String toString() {
    689             return "WindowState: {" + mAppToken + " " + mName
    690                     + (mStarting ? " STARTING" : "") + (mExiting ? " EXITING" : "") + "}"
    691                     + " cf=" + mContainingFrame + " pf=" + mParentFrame;
    692         }
    693     }
    694 }
    695