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) {
diff --git a/services/tests/servicestests/src/com/android/server/wm/AppWindowTokenTests.java b/services/tests/servicestests/src/com/android/server/wm/AppWindowTokenTests.java
index d933cb3..772b2a2 100644
--- a/services/tests/servicestests/src/com/android/server/wm/AppWindowTokenTests.java
+++ b/services/tests/servicestests/src/com/android/server/wm/AppWindowTokenTests.java
@@ -33,12 +33,11 @@
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
/**
- * Tests for the {@link WindowState} class.
+ * Tests for the {@link AppWindowToken} class.
*
* Build/Install/Run:
* bit FrameworksServicesTests:com.android.server.wm.AppWindowTokenTests
diff --git a/services/tests/servicestests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/servicestests/src/com/android/server/wm/DisplayContentTests.java
new file mode 100644
index 0000000..0533efc
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/wm/DisplayContentTests.java
@@ -0,0 +1,159 @@
+/*
+ * 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 com.android.server.wm;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import android.content.Context;
+import android.content.res.Configuration;
+import android.os.IBinder;
+import android.platform.test.annotations.Presubmit;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.view.Display;
+import android.view.IWindow;
+import android.view.WindowManager;
+
+import java.util.ArrayList;
+
+import static android.app.ActivityManager.StackId.FIRST_DYNAMIC_STACK_ID;
+import static android.app.AppOpsManager.OP_NONE;
+import static android.content.res.Configuration.EMPTY;
+import static android.view.WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW;
+import static android.view.WindowManager.LayoutParams.LAST_APPLICATION_WINDOW;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA;
+import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
+import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
+import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG;
+import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR;
+import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR;
+import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.mock;
+
+/**
+ * Tests for the {@link DisplayContent} class.
+ *
+ * Build/Install/Run:
+ * bit FrameworksServicesTests:com.android.server.wm.DisplayContentTests
+ */
+@SmallTest
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class DisplayContentTests {
+
+ private static WindowManagerService sWm = null;
+ private final IWindow mIWindow = new TestIWindow();
+ private final Session mMockSession = mock(Session.class);
+ private Display mDisplay;
+ private int mNextStackId = FIRST_DYNAMIC_STACK_ID;
+ private int mNextTaskId = 0;
+
+ @Before
+ public void setUp() throws Exception {
+ final Context context = InstrumentationRegistry.getTargetContext();
+ sWm = TestWindowManagerPolicy.getWindowManagerService(context);
+ mDisplay = context.getDisplay();
+ }
+
+ @Test
+ public void testForAllWindows() throws Exception {
+ final DisplayContent dc = new DisplayContent(mDisplay, sWm, null, null);
+ final WindowState wallpaperWindow = createWindow(null, TYPE_WALLPAPER, dc, "wallpaper");
+ final WindowState imeWindow = createWindow(null, TYPE_INPUT_METHOD, dc, "ime");
+ final WindowState imeDialogWindow = createWindow(null, TYPE_INPUT_METHOD_DIALOG, dc,
+ "ime dialog");
+ final WindowState statusBarWindow = createWindow(null, TYPE_STATUS_BAR, dc, "status bar");
+ final WindowState navBarWindow = createWindow(null, TYPE_NAVIGATION_BAR,
+ statusBarWindow.mToken, "nav bar");
+ final WindowState appWindow = createWindow(null, TYPE_BASE_APPLICATION, dc, "app");
+ final WindowState negChildAppWindow = createWindow(appWindow, TYPE_APPLICATION_MEDIA,
+ appWindow.mToken, "negative app child");
+ final WindowState posChildAppWindow = createWindow(appWindow,
+ TYPE_APPLICATION_ATTACHED_DIALOG, appWindow.mToken, "positive app child");
+ final WindowState exitingAppWindow = createWindow(null, TYPE_BASE_APPLICATION, dc,
+ "exiting app");
+ final AppWindowToken exitingAppToken = exitingAppWindow.mAppToken;
+ exitingAppToken.mIsExiting = true;
+ exitingAppToken.mTask.mStack.mExitingAppTokens.add(exitingAppToken);
+
+ final ArrayList<WindowState> windows = new ArrayList();
+
+ // Test forward traversal.
+ dc.forAllWindows(windows::add, false /* traverseTopToBottom */);
+
+ assertEquals(wallpaperWindow, windows.get(0));
+ assertEquals(exitingAppWindow, windows.get(1));
+ assertEquals(negChildAppWindow, windows.get(2));
+ assertEquals(appWindow, windows.get(3));
+ assertEquals(posChildAppWindow, windows.get(4));
+ assertEquals(statusBarWindow, windows.get(5));
+ assertEquals(navBarWindow, windows.get(6));
+ assertEquals(imeWindow, windows.get(7));
+ assertEquals(imeDialogWindow, windows.get(8));
+
+ // Test backward traversal.
+ windows.clear();
+ dc.forAllWindows(windows::add, true /* traverseTopToBottom */);
+
+ assertEquals(wallpaperWindow, windows.get(8));
+ assertEquals(exitingAppWindow, windows.get(7));
+ assertEquals(negChildAppWindow, windows.get(6));
+ assertEquals(appWindow, windows.get(5));
+ assertEquals(posChildAppWindow, windows.get(4));
+ assertEquals(statusBarWindow, windows.get(3));
+ assertEquals(navBarWindow, windows.get(2));
+ assertEquals(imeWindow, windows.get(1));
+ assertEquals(imeDialogWindow, windows.get(0));
+ }
+
+ private WindowToken createWindowToken(DisplayContent dc, int type) {
+ if (type < FIRST_APPLICATION_WINDOW || type > LAST_APPLICATION_WINDOW) {
+ return new WindowToken(sWm, mock(IBinder.class), type, false, dc);
+ }
+
+ final int stackId = mNextStackId++;
+ dc.addStackToDisplay(stackId, true);
+ final TaskStack stack = sWm.mStackIdToStack.get(stackId);
+ final Task task = new Task(mNextTaskId++, stack, 0, sWm, null, EMPTY, false);
+ stack.addTask(task, true);
+ final AppWindowToken token = new AppWindowToken(sWm, null, false, dc);
+ task.addAppToken(0, token, 0, false);
+ return token;
+ }
+
+ private WindowState createWindow(WindowState parent, int type, DisplayContent dc, String name) {
+ final WindowToken token = createWindowToken(dc, type);
+ return createWindow(parent, type, token, name);
+ }
+
+ private WindowState createWindow(WindowState parent, int type, WindowToken token, String name) {
+ final WindowManager.LayoutParams attrs = new WindowManager.LayoutParams(type);
+ attrs.setTitle(name);
+
+ final WindowState w = new WindowState(sWm, mMockSession, mIWindow, token, parent, OP_NONE,
+ 0, attrs, 0, 0);
+ // TODO: Probably better to make this call in the WindowState ctor to avoid errors with
+ // adding it to the token...
+ token.addWindow(w);
+ return w;
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/wm/WindowTokenTests.java b/services/tests/servicestests/src/com/android/server/wm/WindowTokenTests.java
index 5326a19..d12d672a 100644
--- a/services/tests/servicestests/src/com/android/server/wm/WindowTokenTests.java
+++ b/services/tests/servicestests/src/com/android/server/wm/WindowTokenTests.java
@@ -39,7 +39,7 @@
import static org.mockito.Mockito.mock;
/**
- * Tests for the {@link WindowState} class.
+ * Tests for the {@link WindowToken} class.
*
* Build/Install/Run:
* bit FrameworksServicesTests:com.android.server.wm.WindowTokenTests