Traverse window hierarchy without window list
Added support for to get all windows in the hierarchy without needing
to use WindowList concept which is a very complicated implementation
in the code base.
This implementation walks the hierarchy node by node returns windows
in order to the caller using a callback.
Test: bit FrameworksServicesTests:com.android.server.wm.DisplayContentTests
Change-Id: I2719f7c96f26dad23f91c1c589be88712bd224b8
diff --git a/services/core/java/com/android/server/wm/AppWindowToken.java b/services/core/java/com/android/server/wm/AppWindowToken.java
index 622eece..05e6f96 100644
--- a/services/core/java/com/android/server/wm/AppWindowToken.java
+++ b/services/core/java/com/android/server/wm/AppWindowToken.java
@@ -66,6 +66,7 @@
import java.io.PrintWriter;
import java.util.ArrayDeque;
import java.util.ArrayList;
+import java.util.function.Consumer;
class AppTokenList extends ArrayList<AppWindowToken> {
}
@@ -1269,6 +1270,22 @@
}
@Override
+ void forAllWindows(Consumer<WindowState> callback, boolean traverseTopToBottom) {
+ // For legacy reasons we process the TaskStack.mExitingAppTokens first in DisplayContent
+ // before the non-exiting app tokens. So, we skip the exiting app tokens here.
+ // TODO: Investigate if we need to continue to do this or if we can just process them
+ // in-order.
+ if (mIsExiting && !waitingForReplacement()) {
+ return;
+ }
+ forAllWindowsUnchecked(callback, traverseTopToBottom);
+ }
+
+ void forAllWindowsUnchecked(Consumer<WindowState> callback, boolean traverseTopToBottom) {
+ super.forAllWindows(callback, traverseTopToBottom);
+ }
+
+ @Override
AppWindowToken asAppWindowToken() {
// I am an app window token!
return this;
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index ec4cdf2..3ff0e7a 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -140,6 +140,7 @@
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
+import java.util.function.Consumer;
/**
* Utility class for keeping track of the WindowStates and other pertinent contents of a
@@ -3243,6 +3244,42 @@
}
@Override
+ void forAllWindows(Consumer<WindowState> callback, boolean traverseTopToBottom) {
+ if (traverseTopToBottom) {
+ super.forAllWindows(callback, traverseTopToBottom);
+ forAllExitingAppTokenWindows(callback, traverseTopToBottom);
+ } else {
+ forAllExitingAppTokenWindows(callback, traverseTopToBottom);
+ super.forAllWindows(callback, traverseTopToBottom);
+ }
+ }
+
+ private void forAllExitingAppTokenWindows(Consumer<WindowState> callback,
+ boolean traverseTopToBottom) {
+ // For legacy reasons we process the TaskStack.mExitingAppTokens first here before the
+ // app tokens.
+ // TODO: Investigate if we need to continue to do this or if we can just process them
+ // in-order.
+ if (traverseTopToBottom) {
+ for (int i = mChildren.size() - 1; i >= 0; --i) {
+ final AppTokenList appTokens = mChildren.get(i).mExitingAppTokens;
+ for (int j = appTokens.size() - 1; j >= 0; --j) {
+ appTokens.get(j).forAllWindowsUnchecked(callback, traverseTopToBottom);
+ }
+ }
+ } else {
+ final int count = mChildren.size();
+ for (int i = 0; i < count; ++i) {
+ final AppTokenList appTokens = mChildren.get(i).mExitingAppTokens;
+ final int appTokensCount = appTokens.size();
+ for (int j = 0; j < appTokensCount; j++) {
+ appTokens.get(j).forAllWindowsUnchecked(callback, traverseTopToBottom);
+ }
+ }
+ }
+ }
+
+ @Override
int getOrientation() {
if (mService.isStackVisibleLocked(DOCKED_STACK_ID)
|| mService.isStackVisibleLocked(FREEFORM_WORKSPACE_STACK_ID)) {
diff --git a/services/core/java/com/android/server/wm/PinnedStackController.java b/services/core/java/com/android/server/wm/PinnedStackController.java
index 1ccf722..023a699 100644
--- a/services/core/java/com/android/server/wm/PinnedStackController.java
+++ b/services/core/java/com/android/server/wm/PinnedStackController.java
@@ -43,6 +43,7 @@
import com.android.internal.os.BackgroundThread;
import com.android.internal.policy.PipMotionHelper;
import com.android.internal.policy.PipSnapAlgorithm;
+import com.android.server.UiThread;
import java.io.PrintWriter;
@@ -55,7 +56,7 @@
private final WindowManagerService mService;
private final DisplayContent mDisplayContent;
- private final Handler mHandler = new Handler();
+ private final Handler mHandler = UiThread.getHandler();
private IPinnedStackListener mPinnedStackListener;
private final PinnedStackListenerDeathHandler mPinnedStackListenerDeathHandler =
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index e30ebcb..62ad217 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -22,6 +22,7 @@
import java.util.Comparator;
import java.util.LinkedList;
+import java.util.function.Consumer;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_BEHIND;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
@@ -494,6 +495,19 @@
return addIndex;
}
+ void forAllWindows(Consumer<WindowState> callback, boolean traverseTopToBottom) {
+ if (traverseTopToBottom) {
+ for (int i = mChildren.size() - 1; i >= 0; --i) {
+ mChildren.get(i).forAllWindows(callback, traverseTopToBottom);
+ }
+ } else {
+ final int count = mChildren.size();
+ for (int i = 0; i < count; i++) {
+ mChildren.get(i).forAllWindows(callback, traverseTopToBottom);
+ }
+ }
+ }
+
/**
* Returns 1, 0, or -1 depending on if this container is greater than, equal to, or lesser than
* the input container in terms of z-order.
@@ -562,8 +576,7 @@
void dumpChildrenNames(StringBuilder out, String prefix) {
final String childPrefix = prefix + " ";
out.append(getName() + "\n");
- final int count = mChildren.size();
- for (int i = 0; i < count; i++) {
+ for (int i = mChildren.size() - 1; i >= 0; --i) {
final WindowContainer wc = mChildren.get(i);
out.append(childPrefix + "#" + i + " ");
wc.dumpChildrenNames(out, childPrefix);
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 5baa72a..7e5723d 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -8201,6 +8201,8 @@
StringBuilder output = new StringBuilder();
mRoot.dumpChildrenNames(output, " ");
pw.println(output.toString());
+ pw.println(" ");
+ mRoot.forAllWindows(pw::println, true /* traverseTopToBottom */);
}
return;
} else {
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 972c359..3c2a542 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -59,6 +59,7 @@
import java.util.ArrayList;
import java.util.Comparator;
import java.util.LinkedList;
+import java.util.function.Consumer;
import static android.app.ActivityManager.StackId;
import static android.app.ActivityManager.StackId.DOCKED_STACK_ID;
@@ -3876,6 +3877,78 @@
return index;
}
+ @Override
+ void forAllWindows(Consumer<WindowState> callback, boolean traverseTopToBottom) {
+ if (mChildren.isEmpty()) {
+ // The window has no children so we just return it.
+ callback.accept(this);
+ return;
+ }
+
+ if (traverseTopToBottom) {
+ forAllWindowTopToBottom(callback);
+ } else {
+ forAllWindowBottomToTop(callback);
+ }
+ }
+
+ private void forAllWindowBottomToTop(Consumer<WindowState> callback) {
+ // We want to consumer the negative sublayer children first because they need to appear
+ // below the parent, then this window (the parent), and then the positive sublayer children
+ // because they need to appear above the parent.
+ int i = 0;
+ final int count = mChildren.size();
+ WindowState child = mChildren.get(i);
+
+ while (i < count && child.mSubLayer < 0) {
+ callback.accept(child);
+ i++;
+ if (i >= count) {
+ break;
+ }
+ child = mChildren.get(i);
+ }
+
+ callback.accept(this);
+
+ while (i < count) {
+ callback.accept(child);
+ i++;
+ if (i >= count) {
+ break;
+ }
+ child = mChildren.get(i);
+ }
+ }
+
+ private void forAllWindowTopToBottom(Consumer<WindowState> callback) {
+ // We want to consumer the positive sublayer children first because they need to appear
+ // above the parent, then this window (the parent), and then the negative sublayer children
+ // because they need to appear above the parent.
+ int i = mChildren.size() - 1;
+ WindowState child = mChildren.get(i);
+
+ while (i >= 0 && child.mSubLayer >= 0) {
+ callback.accept(child);
+ --i;
+ if (i < 0) {
+ break;
+ }
+ child = mChildren.get(i);
+ }
+
+ callback.accept(this);
+
+ while (i >= 0) {
+ callback.accept(child);
+ --i;
+ if (i < 0) {
+ break;
+ }
+ child = mChildren.get(i);
+ }
+ }
+
boolean isWindowAnimationSet() {
if (mWinAnimator.isWindowAnimationSet()) {
return true;
diff --git a/services/core/java/com/android/server/wm/WindowToken.java b/services/core/java/com/android/server/wm/WindowToken.java
index 93f1610..402bcfb 100644
--- a/services/core/java/com/android/server/wm/WindowToken.java
+++ b/services/core/java/com/android/server/wm/WindowToken.java
@@ -196,9 +196,8 @@
*/
protected boolean isFirstChildWindowGreaterThanSecond(WindowState newWindow,
WindowState existingWindow) {
- // By default the first window isn't greater than the second to preserve existing logic of
- // how new windows are added to the token
- return false;
+ // New window is considered greater if it has a higher or equal base layer.
+ return newWindow.mBaseLayer >= existingWindow.mBaseLayer;
}
void addWindow(final WindowState win) {