blob: 3f66c18991a8959dc3068df75db5000525e1505d [file] [log] [blame]
/*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License
*/
package android.server.cts;
import com.android.tradefed.device.CollectingOutputReceiver;
import com.android.tradefed.device.DeviceNotAvailableException;
import com.android.tradefed.device.ITestDevice;
import com.android.tradefed.log.LogUtil.CLog;
import java.awt.Rectangle;
import java.lang.String;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.regex.Pattern;
import java.util.regex.Matcher;
import static android.server.cts.StateLogger.log;
import static android.server.cts.StateLogger.logE;
class WindowManagerState {
private static final String DUMPSYS_WINDOWS_APPS = "dumpsys window apps";
private static final String DUMPSYS_WINDOWS_VISIBLE_APPS = "dumpsys window visible-apps";
private static final Pattern sWindowPattern =
Pattern.compile("Window #(\\d+) Window\\{([0-9a-fA-F]+) u(\\d+) (.+)\\}\\:");
private static final Pattern sStartingWindowPattern =
Pattern.compile("Window #(\\d+) Window\\{([0-9a-fA-F]+) u(\\d+) Starting (.+)\\}\\:");
private static final Pattern sExitingWindowPattern =
Pattern.compile("Window #(\\d+) Window\\{([0-9a-fA-F]+) u(\\d+) (.+) EXITING\\}\\:");
private static final Pattern sFocusedWindowPattern = Pattern.compile(
"mCurrentFocus=Window\\{([0-9a-fA-F]+) u(\\d+) (\\S+)\\}");
private static final Pattern sAppErrorFocusedWindowPattern = Pattern.compile(
"mCurrentFocus=Window\\{([0-9a-fA-F]+) u(\\d+) Application Error\\: (\\S+)\\}");
private static final Pattern sFocusedAppPattern =
Pattern.compile("mFocusedApp=AppWindowToken\\{(.+) token=Token\\{(.+) "
+ "ActivityRecord\\{(.+) u(\\d+) (\\S+) (\\S+)");
private static final Pattern sStackIdPattern = Pattern.compile("mStackId=(\\d+)");
private static final Pattern[] sExtractStackExitPatterns = {
sStackIdPattern, sWindowPattern, sStartingWindowPattern, sExitingWindowPattern,
sFocusedWindowPattern, sAppErrorFocusedWindowPattern, sFocusedAppPattern };
// Windows in z-order with the top most at the front of the list.
private List<String> mWindows = new ArrayList();
private List<WindowState> mWindowStates = new ArrayList();
private List<WindowStack> mStacks = new ArrayList();
private List<Display> mDisplays = new ArrayList();
private String mFocusedWindow = null;
private String mFocusedApp = null;
private final LinkedList<String> mSysDump = new LinkedList();
void computeState(ITestDevice device, boolean visibleOnly) throws DeviceNotAvailableException {
// It is possible the system is in the middle of transition to the right state when we get
// the dump. We try a few times to get the information we need before giving up.
int retriesLeft = 3;
boolean retry = false;
String dump = null;
log("==============================");
log(" WindowManagerState ");
log("==============================");
do {
if (retry) {
log("***Incomplete WM state. Retrying...");
// Wait half a second between retries for window manager to finish transitioning...
try {
Thread.sleep(500);
} catch (InterruptedException e) {
log(e.toString());
// Well I guess we are not waiting...
}
}
final CollectingOutputReceiver outputReceiver = new CollectingOutputReceiver();
final String dumpsysCmd = visibleOnly ?
DUMPSYS_WINDOWS_VISIBLE_APPS : DUMPSYS_WINDOWS_APPS;
device.executeShellCommand(dumpsysCmd, outputReceiver);
dump = outputReceiver.getOutput();
parseSysDump(dump, visibleOnly);
retry = mWindows.isEmpty() || mFocusedWindow == null || mFocusedApp == null;
} while (retry && retriesLeft-- > 0);
if (retry) {
log(dump);
}
if (mWindows.isEmpty()) {
logE("No Windows found...");
}
if (mFocusedWindow == null) {
logE("No Focused Window...");
}
if (mFocusedApp == null) {
logE("No Focused App...");
}
}
private void parseSysDump(String sysDump, boolean visibleOnly) {
reset();
Collections.addAll(mSysDump, sysDump.split("\\n"));
while (!mSysDump.isEmpty()) {
final Display display =
Display.create(mSysDump, sExtractStackExitPatterns);
if (display != null) {
log(display.toString());
mDisplays.add(display);
continue;
}
final WindowStack stack =
WindowStack.create(mSysDump, sStackIdPattern, sExtractStackExitPatterns);
if (stack != null) {
mStacks.add(stack);
continue;
}
final WindowState ws = WindowState.create(mSysDump, sExtractStackExitPatterns);
if (ws != null) {
log(ws.toString());
if (visibleOnly) {
// Check to see if we are in the middle of transitioning. If we are, we want to
// skip dumping until window manager is done transitioning windows.
if (ws.isStartingWindow()) {
log("Skipping dump due to starting window transition...");
return;
}
if (ws.isExitingWindow()) {
log("Skipping dump due to exiting window transition...");
return;
}
}
mWindows.add(ws.getName());
mWindowStates.add(ws);
continue;
}
final String line = mSysDump.pop().trim();
Matcher matcher = sFocusedWindowPattern.matcher(line);
if (matcher.matches()) {
log(line);
final String focusedWindow = matcher.group(3);
log(focusedWindow);
mFocusedWindow = focusedWindow;
continue;
}
matcher = sAppErrorFocusedWindowPattern.matcher(line);
if (matcher.matches()) {
log(line);
final String focusedWindow = matcher.group(3);
log(focusedWindow);
mFocusedWindow = focusedWindow;
continue;
}
matcher = sFocusedAppPattern.matcher(line);
if (matcher.matches()) {
log(line);
final String focusedApp = matcher.group(5);
log(focusedApp);
mFocusedApp = focusedApp;
continue;
}
}
}
void getMatchingWindowTokens(final String windowName, List<String> tokenList) {
tokenList.clear();
for (WindowState ws : mWindowStates) {
if (windowName.equals(ws.getName())) {
tokenList.add(ws.getToken());
}
}
}
void getMatchingWindowState(final String windowName, List<WindowState> windowList) {
windowList.clear();
for (WindowState ws : mWindowStates) {
if (windowName.equals(ws.getName())) {
windowList.add(ws);
}
}
}
Display getDisplay(int displayId) {
for (Display display : mDisplays) {
if (displayId == display.getDisplayId()) {
return display;
}
}
return null;
}
String getFrontWindow() {
if (mWindows == null || mWindows.isEmpty()) {
return null;
}
return mWindows.get(0);
}
String getFocusedWindow() {
return mFocusedWindow;
}
String getFocusedApp() {
return mFocusedApp;
}
int getFrontStackId() {
return mStacks.get(0).mStackId;
}
boolean containsStack(int stackId) {
for (WindowStack stack : mStacks) {
if (stackId == stack.mStackId) {
return true;
}
}
return false;
}
boolean isWindowVisible(String windowName) {
for (String window : mWindows) {
if (window.equals(windowName)) {
return true;
}
}
return false;
}
WindowStack getStack(int stackId) {
for (WindowStack stack : mStacks) {
if (stackId == stack.mStackId) {
return stack;
}
}
return null;
}
private void reset() {
mSysDump.clear();
mStacks.clear();
mDisplays.clear();
mWindows.clear();
mWindowStates.clear();
mFocusedWindow = null;
mFocusedApp = null;
}
static class WindowStack extends WindowContainer {
private static final Pattern sTaskIdPattern = Pattern.compile("taskId=(\\d+)");
int mStackId;
ArrayList<WindowTask> mTasks = new ArrayList();
private WindowStack() {
}
static WindowStack create(
LinkedList<String> dump, Pattern stackIdPattern, Pattern[] exitPatterns) {
final String line = dump.peek().trim();
final Matcher matcher = stackIdPattern.matcher(line);
if (!matcher.matches()) {
// Not a stack.
return null;
}
// For the stack Id line we just read.
dump.pop();
final WindowStack stack = new WindowStack();
log(line);
final String stackId = matcher.group(1);
log(stackId);
stack.mStackId = Integer.parseInt(stackId);
stack.extract(dump, exitPatterns);
return stack;
}
void extract(LinkedList<String> dump, Pattern[] exitPatterns) {
final List<Pattern> taskExitPatterns = new ArrayList();
Collections.addAll(taskExitPatterns, exitPatterns);
taskExitPatterns.add(sTaskIdPattern);
final Pattern[] taskExitPatternsArray =
taskExitPatterns.toArray(new Pattern[taskExitPatterns.size()]);
while (!doneExtracting(dump, exitPatterns)) {
final WindowTask task =
WindowTask.create(dump, sTaskIdPattern, taskExitPatternsArray);
if (task != null) {
mTasks.add(task);
continue;
}
final String line = dump.pop().trim();
if (extractFullscreen(line)) {
continue;
}
if (extractBounds(line)) {
continue;
}
}
}
WindowTask getTask(int taskId) {
for (WindowTask task : mTasks) {
if (taskId == task.mTaskId) {
return task;
}
}
return null;
}
}
static class WindowTask extends WindowContainer {
private static final Pattern sTempInsetBoundsPattern =
Pattern.compile("mTempInsetBounds=\\[(\\d+),(\\d+)\\]\\[(\\d+),(\\d+)\\]");
private static final Pattern sAppTokenPattern = Pattern.compile(
"Activity #(\\d+) AppWindowToken\\{(\\S+) token=Token\\{(\\S+) "
+ "ActivityRecord\\{(\\S+) u(\\d+) (\\S+) t(\\d+)\\}\\}\\}");
int mTaskId;
Rectangle mTempInsetBounds;
List<String> mAppTokens = new ArrayList();
private WindowTask() {
}
static WindowTask create(
LinkedList<String> dump, Pattern taskIdPattern, Pattern[] exitPatterns) {
final String line = dump.peek().trim();
final Matcher matcher = taskIdPattern.matcher(line);
if (!matcher.matches()) {
// Not a task.
return null;
}
// For the task Id line we just read.
dump.pop();
final WindowTask task = new WindowTask();
log(line);
final String taskId = matcher.group(1);
log(taskId);
task.mTaskId = Integer.parseInt(taskId);
task.extract(dump, exitPatterns);
return task;
}
private void extract(LinkedList<String> dump, Pattern[] exitPatterns) {
while (!doneExtracting(dump, exitPatterns)) {
final String line = dump.pop().trim();
if (extractFullscreen(line)) {
continue;
}
if (extractBounds(line)) {
continue;
}
Matcher matcher = sTempInsetBoundsPattern.matcher(line);
if (matcher.matches()) {
log(line);
mTempInsetBounds = extractBounds(matcher);
}
matcher = sAppTokenPattern.matcher(line);
if (matcher.matches()) {
log(line);
final String appToken = matcher.group(6);
log(appToken);
mAppTokens.add(appToken);
continue;
}
}
}
}
static abstract class WindowContainer {
protected static final Pattern sFullscreenPattern = Pattern.compile("mFullscreen=(\\S+)");
protected static final Pattern sBoundsPattern =
Pattern.compile("mBounds=\\[(-?\\d+),(-?\\d+)\\]\\[(-?\\d+),(-?\\d+)\\]");
protected boolean mFullscreen;
protected Rectangle mBounds;
static boolean doneExtracting(LinkedList<String> dump, Pattern[] exitPatterns) {
if (dump.isEmpty()) {
return true;
}
final String line = dump.peek().trim();
for (Pattern pattern : exitPatterns) {
if (pattern.matcher(line).matches()) {
return true;
}
}
return false;
}
boolean extractFullscreen(String line) {
final Matcher matcher = sFullscreenPattern.matcher(line);
if (!matcher.matches()) {
return false;
}
log(line);
final String fullscreen = matcher.group(1);
log(fullscreen);
mFullscreen = Boolean.valueOf(fullscreen);
return true;
}
boolean extractBounds(String line) {
final Matcher matcher = sBoundsPattern.matcher(line);
if (!matcher.matches()) {
return false;
}
log(line);
mBounds = extractBounds(matcher);
return true;
}
static Rectangle extractBounds(Matcher matcher) {
final int left = Integer.valueOf(matcher.group(1));
final int top = Integer.valueOf(matcher.group(2));
final int right = Integer.valueOf(matcher.group(3));
final int bottom = Integer.valueOf(matcher.group(4));
final Rectangle rect = new Rectangle(left, top, right - left, bottom - top);
log(rect.toString());
return rect;
}
static void extractMultipleBounds(Matcher matcher, int groupIndex, Rectangle... rectList) {
for (Rectangle rect : rectList) {
if (rect == null) {
return;
}
final int left = Integer.valueOf(matcher.group(groupIndex++));
final int top = Integer.valueOf(matcher.group(groupIndex++));
final int right = Integer.valueOf(matcher.group(groupIndex++));
final int bottom = Integer.valueOf(matcher.group(groupIndex++));
rect.setBounds(left, top, right - left, bottom - top);
}
}
Rectangle getBounds() {
return mBounds;
}
boolean isFullscreen() {
return mFullscreen;
}
}
static class Display extends WindowContainer {
private static final String TAG = "[Display] ";
private static final Pattern sDisplayIdPattern =
Pattern.compile("Display: mDisplayId=(\\d+)");
private static final Pattern sDisplayInfoPattern =
Pattern.compile("(.+) (\\d+)dpi cur=(\\d+)x(\\d+) app=(\\d+)x(\\d+) (.+)");
private final int mDisplayId;
private Rectangle mDisplayRect = new Rectangle();
private Rectangle mAppRect = new Rectangle();
private int mDpi;
private Display(int displayId) {
mDisplayId = displayId;
}
int getDisplayId() {
return mDisplayId;
}
int getDpi() {
return mDpi;
}
Rectangle getDisplayRect() {
return mDisplayRect;
}
Rectangle getAppRect() {
return mAppRect;
}
static Display create(LinkedList<String> dump, Pattern[] exitPatterns) {
// TODO: exit pattern for displays?
final String line = dump.peek().trim();
Matcher matcher = sDisplayIdPattern.matcher(line);
if (!matcher.matches()) {
return null;
}
log(TAG + "DISPLAY_ID: " + line);
dump.pop();
final int displayId = Integer.valueOf(matcher.group(1));
final Display display = new Display(displayId);
display.extract(dump, exitPatterns);
return display;
}
private void extract(LinkedList<String> dump, Pattern[] exitPatterns) {
while (!doneExtracting(dump, exitPatterns)) {
final String line = dump.pop().trim();
final Matcher matcher = sDisplayInfoPattern.matcher(line);
if (matcher.matches()) {
log(TAG + "DISPLAY_INFO: " + line);
mDpi = Integer.valueOf(matcher.group(2));
final int displayWidth = Integer.valueOf(matcher.group(3));
final int displayHeight = Integer.valueOf(matcher.group(4));
mDisplayRect.setBounds(0, 0, displayWidth, displayHeight);
final int appWidth = Integer.valueOf(matcher.group(5));
final int appHeight = Integer.valueOf(matcher.group(6));
mAppRect.setBounds(0, 0, appWidth, appHeight);
// break as we don't need other info for now
break;
}
// Extract other info here if needed
}
}
@Override
public String toString() {
return "Display #" + mDisplayId + ": mDisplayRect=" + mDisplayRect
+ " mAppRect=" + mAppRect;
}
}
static class WindowState extends WindowContainer {
private static final String TAG = "[WindowState] ";
private static final String RECT_STR = "\\[(\\d+),(\\d+)\\]\\[(\\d+),(\\d+)\\]";
private static final Pattern sFramePattern =
Pattern.compile("Frames: containing=" + RECT_STR + " parent=" + RECT_STR);
private static final Pattern sWindowAssociationPattern =
Pattern.compile("mDisplayId=(\\d+) stackId=(\\d+) (.+)");
private final String mName;
private final String mAppToken;
private final boolean mStarting;
private final boolean mExiting;
private int mDisplayId;
private int mStackId;
private Rectangle mContainingFrame = new Rectangle();
private Rectangle mParentFrame = new Rectangle();
private WindowState(Matcher matcher, boolean starting, boolean exiting) {
mName = matcher.group(4);
mAppToken = matcher.group(2);
mStarting = starting;
mExiting = exiting;
}
String getName() {
return mName;
}
String getToken() {
return mAppToken;
}
boolean isStartingWindow() {
return mStarting;
}
boolean isExitingWindow() {
return mExiting;
}
int getDisplayId() {
return mDisplayId;
}
int getStackId() {
return mStackId;
}
Rectangle getContainingFrame() {
return mContainingFrame;
}
Rectangle getParentFrame() {
return mParentFrame;
}
static WindowState create(LinkedList<String> dump, Pattern[] exitPatterns) {
final String line = dump.peek().trim();
Matcher matcher = sWindowPattern.matcher(line);
if (!matcher.matches()) {
return null;
}
log(TAG + "WINDOW: " + line);
dump.pop();
final WindowState window;
Matcher specialMatcher = sStartingWindowPattern.matcher(line);
if (specialMatcher.matches()) {
log(TAG + "STARTING: " + line);
window = new WindowState(specialMatcher, true, false);
} else {
specialMatcher = sExitingWindowPattern.matcher(line);
if (specialMatcher.matches()) {
log(TAG + "EXITING: " + line);
window = new WindowState(specialMatcher, false, true);
} else {
window = new WindowState(matcher, false, false);
}
}
window.extract(dump, exitPatterns);
return window;
}
private void extract(LinkedList<String> dump, Pattern[] exitPatterns) {
while (!doneExtracting(dump, exitPatterns)) {
final String line = dump.pop().trim();
Matcher matcher = sWindowAssociationPattern.matcher(line);
if (matcher.matches()) {
log(TAG + "WINDOW_ASSOCIATION: " + line);
mDisplayId = Integer.valueOf(matcher.group(1));
mStackId = Integer.valueOf(matcher.group(2));
continue;
}
matcher = sFramePattern.matcher(line);
if (matcher.matches()) {
log(TAG + "FRAME: " + line);
extractMultipleBounds(matcher, 1, mContainingFrame, mParentFrame);
continue;
}
// Extract other info here if needed
}
}
@Override
public String toString() {
return "WindowState: {" + mAppToken + " " + mName
+ (mStarting ? " STARTING" : "") + (mExiting ? " EXITING" : "") + "}"
+ " cf=" + mContainingFrame + " pf=" + mParentFrame;
}
}
}