blob: 3cd1f5791d69d0124fbb4d49dd82c4a7ce4d6e80 [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.am;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.server.am.ProtoExtractors.extract;
import static android.server.am.StateLogger.log;
import static android.server.am.StateLogger.logE;
import static android.view.Display.DEFAULT_DISPLAY;
import android.content.res.Configuration;
import android.graphics.Rect;
import android.os.ParcelFileDescriptor;
import android.os.SystemClock;
import android.view.nano.DisplayInfoProto;
import androidx.test.InstrumentationRegistry;
import com.android.server.wm.nano.AppTransitionProto;
import com.android.server.wm.nano.AppWindowTokenProto;
import com.android.server.wm.nano.ConfigurationContainerProto;
import com.android.server.wm.nano.DisplayProto;
import com.android.server.wm.nano.IdentifierProto;
import com.android.server.wm.nano.PinnedStackControllerProto;
import com.android.server.wm.nano.StackProto;
import com.android.server.wm.nano.TaskProto;
import com.android.server.wm.nano.WindowContainerProto;
import com.android.server.wm.nano.WindowManagerServiceDumpProto;
import com.android.server.wm.nano.WindowStateAnimatorProto;
import com.android.server.wm.nano.WindowStateProto;
import com.android.server.wm.nano.WindowSurfaceControllerProto;
import com.android.server.wm.nano.WindowTokenProto;
import com.google.protobuf.nano.InvalidProtocolBufferNanoException;
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
public class WindowManagerState {
private static final String DUMPSYS_WINDOW = "dumpsys window -a --proto";
private static final String STARTING_WINDOW_PREFIX = "Starting ";
private static final String DEBUGGER_WINDOW_PREFIX = "Waiting For Debugger: ";
// Windows in z-order with the top most at the front of the list.
private List<WindowState> mWindowStates = new ArrayList();
// Stacks in z-order with the top most at the front of the list, starting with primary display.
private final List<WindowStack> mStacks = new ArrayList();
// Stacks on all attached displays, in z-order with the top most at the front of the list.
private final Map<Integer, List<WindowStack>> mDisplayStacks
= new HashMap<>();
private List<Display> mDisplays = new ArrayList();
private String mFocusedWindow = null;
private String mFocusedApp = null;
private Rect mDefaultPinnedStackBounds = new Rect();
private Rect mPinnedStackMovementBounds = new Rect();
private final LinkedList<String> mSysDump = new LinkedList();
private int mRotation;
private boolean mIsDockedStackMinimized;
public void computeState() {
// 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;
byte[] 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...
SystemClock.sleep(500);
}
dump = executeShellCommand(DUMPSYS_WINDOW);
try {
parseSysDumpProto(dump);
} catch (InvalidProtocolBufferNanoException ex) {
throw new RuntimeException("Failed to parse dumpsys:\n"
+ new String(dump, StandardCharsets.UTF_8), ex);
}
retry = mWindowStates.isEmpty() || mFocusedApp == null;
} while (retry && retriesLeft-- > 0);
if (mWindowStates.isEmpty()) {
logE("No Windows found...");
}
if (mFocusedWindow == null) {
logE("No Focused Window...");
}
if (mFocusedApp == null) {
logE("No Focused App...");
}
}
private byte[] executeShellCommand(String cmd) {
try {
ParcelFileDescriptor pfd =
InstrumentationRegistry.getInstrumentation().getUiAutomation()
.executeShellCommand(cmd);
byte[] buf = new byte[512];
int bytesRead;
FileInputStream fis = new ParcelFileDescriptor.AutoCloseInputStream(pfd);
ByteArrayOutputStream stdout = new ByteArrayOutputStream();
while ((bytesRead = fis.read(buf)) != -1) {
stdout.write(buf, 0, bytesRead);
}
fis.close();
return stdout.toByteArray();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
private void parseSysDumpProto(byte[] sysDump) throws InvalidProtocolBufferNanoException {
reset();
WindowManagerServiceDumpProto state = WindowManagerServiceDumpProto.parseFrom(sysDump);
List<WindowState> allWindows = new ArrayList<>();
Map<String, WindowState> windowMap = new HashMap<>();
if (state.focusedWindow != null) {
mFocusedWindow = state.focusedWindow.title;
}
mFocusedApp = state.focusedApp;
for (int i = 0; i < state.rootWindowContainer.displays.length; i++) {
DisplayProto displayProto = state.rootWindowContainer.displays[i];
final Display display = new Display(displayProto);
mDisplays.add(display);
allWindows.addAll(display.getWindows());
List<WindowStack> stacks = new ArrayList<>();
for (int j = 0; j < displayProto.stacks.length; j++) {
StackProto stackProto = displayProto.stacks[j];
final WindowStack stack = new WindowStack(stackProto);
mStacks.add(stack);
stacks.add(stack);
allWindows.addAll(stack.getWindows());
}
mDisplayStacks.put(display.mDisplayId, stacks);
// use properties from the default display only
if (display.getDisplayId() == DEFAULT_DISPLAY) {
if (displayProto.dockedStackDividerController != null) {
mIsDockedStackMinimized =
displayProto.dockedStackDividerController.minimizedDock;
}
PinnedStackControllerProto pinnedStackProto = displayProto.pinnedStackController;
if (pinnedStackProto != null) {
mDefaultPinnedStackBounds = extract(pinnedStackProto.defaultBounds);
mPinnedStackMovementBounds = extract(pinnedStackProto.movementBounds);
}
}
}
for (WindowState w : allWindows) {
windowMap.put(w.getToken(), w);
}
for (int i = 0; i < state.rootWindowContainer.windows.length; i++) {
IdentifierProto identifierProto = state.rootWindowContainer.windows[i];
String hash_code = Integer.toHexString(identifierProto.hashCode);
mWindowStates.add(windowMap.get(hash_code));
}
mRotation = state.rotation;
AppTransitionProto appTransitionProto = state.appTransition;
}
public void getMatchingVisibleWindowState(final String windowName, List<WindowState> windowList) {
windowList.clear();
for (WindowState ws : mWindowStates) {
if (ws.isShown() && windowName.equals(ws.getName())) {
windowList.add(ws);
}
}
}
public WindowState getWindowByPackageName(String packageName, int windowType) {
for (WindowState ws : mWindowStates) {
final String name = ws.getName();
if (name == null || !name.contains(packageName)) {
continue;
}
if (windowType != ws.getType()) {
continue;
}
return ws;
}
return null;
}
public void getWindowsByPackageName(String packageName, List<Integer> restrictToTypeList,
List<WindowState> outWindowList) {
outWindowList.clear();
for (WindowState ws : mWindowStates) {
final String name = ws.getName();
if (name == null || !name.contains(packageName)) {
continue;
}
if (restrictToTypeList != null && !restrictToTypeList.contains(ws.getType())) {
continue;
}
outWindowList.add(ws);
}
}
Display getDisplay(int displayId) {
for (Display display : mDisplays) {
if (displayId == display.getDisplayId()) {
return display;
}
}
return null;
}
List<Display> getDisplays() {
return mDisplays;
}
String getFrontWindow() {
if (mWindowStates == null || mWindowStates.isEmpty()) {
return null;
}
return mWindowStates.get(0).getName();
}
public String getFocusedWindow() {
return mFocusedWindow;
}
public String getFocusedApp() {
return mFocusedApp;
}
public int getRotation() {
return mRotation;
}
/**
* Check if at least one window which matches provided window name is visible.
*/
boolean isWindowVisible(String windowName) {
for (WindowState window : mWindowStates) {
if (window.getName().equals(windowName)) {
if (window.isShown()) {
return true;
}
}
}
return false;
}
WindowStack getStack(int stackId) {
for (WindowStack stack : mStacks) {
if (stackId == stack.mStackId) {
return stack;
}
}
return null;
}
public boolean isDockedStackMinimized() {
return mIsDockedStackMinimized;
}
public int getZOrder(WindowState w) {
return mWindowStates.size() - mWindowStates.indexOf(w);
}
private void reset() {
mSysDump.clear();
mStacks.clear();
mDisplays.clear();
mWindowStates.clear();
mDisplayStacks.clear();
mFocusedWindow = null;
mFocusedApp = null;
mIsDockedStackMinimized = false;
mDefaultPinnedStackBounds.setEmpty();
mPinnedStackMovementBounds.setEmpty();
mRotation = 0;
}
static class WindowStack extends WindowContainer {
int mStackId;
ArrayList<WindowTask> mTasks = new ArrayList<>();
boolean mWindowAnimationBackgroundSurfaceShowing;
WindowStack(StackProto proto) {
super(proto.windowContainer);
mStackId = proto.id;
mFullscreen = proto.fillsParent;
mBounds = extract(proto.bounds);
for (int i = 0; i < proto.tasks.length; i++) {
TaskProto taskProto = proto.tasks[i];
WindowTask task = new WindowTask(taskProto);
mTasks.add(task);
mSubWindows.addAll(task.getWindows());
}
mWindowAnimationBackgroundSurfaceShowing = proto.animationBackgroundSurfaceIsDimming;
}
WindowTask getTask(int taskId) {
for (WindowTask task : mTasks) {
if (taskId == task.mTaskId) {
return task;
}
}
return null;
}
}
static class WindowTask extends WindowContainer {
int mTaskId;
Rect mTempInsetBounds;
List<String> mAppTokens = new ArrayList<>();
WindowTask(TaskProto proto) {
super(proto.windowContainer);
mTaskId = proto.id;
mFullscreen = proto.fillsParent;
mBounds = extract(proto.bounds);
for (int i = 0; i < proto.appWindowTokens.length; i++) {
AppWindowTokenProto appWindowTokenProto = proto.appWindowTokens[i];
mAppTokens.add(appWindowTokenProto.name);
WindowTokenProto windowTokenProto = appWindowTokenProto.windowToken;
for (int j = 0; j < windowTokenProto.windows.length; j++) {
WindowStateProto windowProto = windowTokenProto.windows[j];
WindowState window = new WindowState(windowProto);
mSubWindows.add(window);
mSubWindows.addAll(window.getWindows());
}
}
mTempInsetBounds = extract(proto.tempInsetBounds);
}
}
static class ConfigurationContainer {
final Configuration mOverrideConfiguration = new Configuration();
final Configuration mFullConfiguration = new Configuration();
final Configuration mMergedOverrideConfiguration = new Configuration();
ConfigurationContainer(ConfigurationContainerProto proto) {
if (proto == null) {
return;
}
mOverrideConfiguration.setTo(extract(proto.overrideConfiguration));
mFullConfiguration.setTo(extract(proto.fullConfiguration));
mMergedOverrideConfiguration.setTo(extract(proto.mergedOverrideConfiguration));
}
int getWindowingMode() {
if (mFullConfiguration == null) {
return WINDOWING_MODE_UNDEFINED;
}
return mFullConfiguration.windowConfiguration.getWindowingMode();
}
int getActivityType() {
if (mFullConfiguration == null) {
return ACTIVITY_TYPE_UNDEFINED;
}
return mFullConfiguration.windowConfiguration.getActivityType();
}
}
static abstract class WindowContainer extends ConfigurationContainer {
protected boolean mFullscreen;
protected Rect mBounds;
protected int mOrientation;
protected List<WindowState> mSubWindows = new ArrayList<>();
WindowContainer(WindowContainerProto proto) {
super(proto.configurationContainer);
mOrientation = proto.orientation;
}
Rect getBounds() {
return mBounds;
}
boolean isFullscreen() {
return mFullscreen;
}
List<WindowState> getWindows() {
return mSubWindows;
}
}
static class Display extends WindowContainer {
private final int mDisplayId;
private Rect mDisplayRect = new Rect();
private Rect mAppRect = new Rect();
private int mDpi;
private String mName;
public Display(DisplayProto proto) {
super(proto.windowContainer);
mDisplayId = proto.id;
for (int i = 0; i < proto.aboveAppWindows.length; i++) {
addWindowsFromTokenProto(proto.aboveAppWindows[i]);
}
for (int i = 0; i < proto.belowAppWindows.length; i++) {
addWindowsFromTokenProto(proto.belowAppWindows[i]);
}
for (int i = 0; i < proto.imeWindows.length; i++) {
addWindowsFromTokenProto(proto.imeWindows[i]);
}
mDpi = proto.dpi;
DisplayInfoProto infoProto = proto.displayInfo;
if (infoProto != null) {
mDisplayRect.set(0, 0, infoProto.logicalWidth, infoProto.logicalHeight);
mAppRect.set(0, 0, infoProto.appWidth, infoProto.appHeight);
mName = infoProto.name;
}
}
private void addWindowsFromTokenProto(WindowTokenProto proto) {
for (int j = 0; j < proto.windows.length; j++) {
WindowStateProto windowProto = proto.windows[j];
WindowState childWindow = new WindowState(windowProto);
mSubWindows.add(childWindow);
mSubWindows.addAll(childWindow.getWindows());
}
}
int getDisplayId() {
return mDisplayId;
}
int getDpi() {
return mDpi;
}
Rect getDisplayRect() {
return mDisplayRect;
}
String getName() {
return mName;
}
@Override
public String toString() {
return "Display #" + mDisplayId + ": name=" + mName + " mDisplayRect=" + mDisplayRect
+ " mAppRect=" + mAppRect;
}
}
public static class WindowState extends WindowContainer {
private static final int WINDOW_TYPE_STARTING = 1;
private static final int WINDOW_TYPE_EXITING = 2;
private static final int WINDOW_TYPE_DEBUGGER = 3;
private String mName;
private final String mAppToken;
private final int mWindowType;
private int mType = 0;
private int mDisplayId;
private int mStackId;
private boolean mShown;
private Rect mContainingFrame = new Rect();
private Rect mParentFrame = new Rect();
private Rect mContentFrame = new Rect();
private Rect mFrame = new Rect();
private Rect mCrop = new Rect();
WindowState(WindowStateProto proto) {
super(proto.windowContainer);
IdentifierProto identifierProto = proto.identifier;
mName = identifierProto.title;
mAppToken = Integer.toHexString(identifierProto.hashCode);
mDisplayId = proto.displayId;
mStackId = proto.stackId;
if (proto.attributes != null) {
mType = proto.attributes.type;
}
WindowStateAnimatorProto animatorProto = proto.animator;
if (animatorProto != null) {
if (animatorProto.surface != null) {
WindowSurfaceControllerProto surfaceProto = animatorProto.surface;
mShown = surfaceProto.shown;
}
mCrop = extract(animatorProto.lastClipRect);
}
mFrame = extract(proto.frame);
mContainingFrame = extract(proto.containingFrame);
mParentFrame = extract(proto.parentFrame);
mContentFrame = extract(proto.contentFrame);
if (mName.startsWith(STARTING_WINDOW_PREFIX)) {
mWindowType = WINDOW_TYPE_STARTING;
// Existing code depends on the prefix being removed
mName = mName.substring(STARTING_WINDOW_PREFIX.length());
} else if (proto.animatingExit) {
mWindowType = WINDOW_TYPE_EXITING;
} else if (mName.startsWith(DEBUGGER_WINDOW_PREFIX)) {
mWindowType = WINDOW_TYPE_STARTING;
mName = mName.substring(DEBUGGER_WINDOW_PREFIX.length());
} else {
mWindowType = 0;
}
for (int i = 0; i < proto.childWindows.length; i++) {
WindowStateProto childProto = proto.childWindows[i];
WindowState childWindow = new WindowState(childProto);
mSubWindows.add(childWindow);
mSubWindows.addAll(childWindow.getWindows());
}
}
public String getName() {
return mName;
}
String getToken() {
return mAppToken;
}
int getDisplayId() {
return mDisplayId;
}
int getStackId() {
return mStackId;
}
public Rect getFrame() {
return mFrame;
}
public Rect getContentFrame() {
return mContentFrame;
}
Rect getCrop() {
return mCrop;
}
public boolean isShown() {
return mShown;
}
public int getType() {
return mType;
}
private String getWindowTypeSuffix(int windowType) {
switch (windowType) {
case WINDOW_TYPE_STARTING:
return " STARTING";
case WINDOW_TYPE_EXITING:
return " EXITING";
case WINDOW_TYPE_DEBUGGER:
return " DEBUGGER";
default:
break;
}
return "";
}
@Override
public String toString() {
return "WindowState: {" + mAppToken + " " + mName
+ getWindowTypeSuffix(mWindowType) + "}" + " type=" + mType
+ " cf=" + mContainingFrame + " pf=" + mParentFrame;
}
}
}