Made WindowState.mChildWindow private scoped

This involved:
- Moving method that operated on mChildWindows and mostly touched
WindowState fields into WindowState class.
- Creating new methods for getting information about child windows
outside WindowState class instead of accessing the child list directly.
- And, tests ;)

Bug: 30060889
Change-Id: I339cecb926b44b5db7ead1970e356304d5429c8f
diff --git a/services/core/java/com/android/server/wm/AccessibilityController.java b/services/core/java/com/android/server/wm/AccessibilityController.java
index 894c27c..1d6cc32 100644
--- a/services/core/java/com/android/server/wm/AccessibilityController.java
+++ b/services/core/java/com/android/server/wm/AccessibilityController.java
@@ -1206,33 +1206,10 @@
                     (int) windowFrame.right, (int) windowFrame.bottom);
         }
 
-        private static WindowInfo obtainPopulatedWindowInfo(WindowState windowState,
-                Rect boundsInScreen) {
-            WindowInfo window = WindowInfo.obtain();
-            window.type = windowState.mAttrs.type;
-            window.layer = windowState.mLayer;
-            window.token = windowState.mClient.asBinder();
-            window.title = windowState.mAttrs.accessibilityTitle;
-            window.accessibilityIdOfAnchor = windowState.mAttrs.accessibilityIdOfAnchor;
-
-            if (windowState.isChildWindow()) {
-                window.parentToken = windowState.mParentWindow.mClient.asBinder();
-            }
-
-            window.focused = windowState.isFocused();
+        private static WindowInfo obtainPopulatedWindowInfo(
+                WindowState windowState, Rect boundsInScreen) {
+            final WindowInfo window = windowState.getWindowInfo();
             window.boundsInScreen.set(boundsInScreen);
-
-            final int childCount = windowState.mChildWindows.size();
-            if (childCount > 0) {
-                if (window.childTokens == null) {
-                    window.childTokens = new ArrayList<IBinder>();
-                }
-                for (int j = 0; j < childCount; j++) {
-                    WindowState child = windowState.mChildWindows.get(j);
-                    window.childTokens.add(child.mClient.asBinder());
-                }
-            }
-
             return window;
         }
 
diff --git a/services/core/java/com/android/server/wm/AppWindowAnimator.java b/services/core/java/com/android/server/wm/AppWindowAnimator.java
index 2c0bb92..39a549d 100644
--- a/services/core/java/com/android/server/wm/AppWindowAnimator.java
+++ b/services/core/java/com/android/server/wm/AppWindowAnimator.java
@@ -424,7 +424,7 @@
 
         final int numAllAppWinAnimators = mAllAppWinAnimators.size();
         for (int i = 0; i < numAllAppWinAnimators; i++) {
-            mAllAppWinAnimators.get(i).finishExit();
+            mAllAppWinAnimators.get(i).mWin.onExitAnimationDone();
         }
         mService.mAppTransition.notifyAppTransitionFinishedLocked(mAppToken.token);
         return false;
diff --git a/services/core/java/com/android/server/wm/AppWindowToken.java b/services/core/java/com/android/server/wm/AppWindowToken.java
index e464ffa..05ce7ae 100644
--- a/services/core/java/com/android/server/wm/AppWindowToken.java
+++ b/services/core/java/com/android/server/wm/AppWindowToken.java
@@ -362,7 +362,7 @@
 
             win.destroyOrSaveSurface();
             if (win.mRemoveOnExit) {
-                service.removeWindowInnerLocked(win);
+                win.removeLocked();
             }
             final DisplayContent displayContent = win.getDisplayContent();
             if (displayContent != null && !displayList.contains(displayContent)) {
@@ -680,10 +680,10 @@
                 candidate.mReplacingWindow.mSkipEnterAnimationForSeamlessReplacement = false;
             }
             // Since the window already timed out, remove it immediately now.
-            // Use removeWindowInnerLocked() instead of removeWindowLocked(), as the latter
+            // Use WindowState#removeLocked() instead of removeWindowLocked(), as the latter
             // delays removal on certain conditions, which will leave the stale window in the
             // stack and marked mWillReplaceWindow=false, so the window will never be removed.
-            service.removeWindowInnerLocked(candidate);
+            candidate.removeLocked();
         }
     }
 
diff --git a/services/core/java/com/android/server/wm/WindowLayersController.java b/services/core/java/com/android/server/wm/WindowLayersController.java
index 072e10f..7405cf9 100644
--- a/services/core/java/com/android/server/wm/WindowLayersController.java
+++ b/services/core/java/com/android/server/wm/WindowLayersController.java
@@ -119,18 +119,13 @@
         mInputMethodAnimLayerAdjustment = adj;
         final WindowState imw = mService.mInputMethodWindow;
         if (imw != null) {
-            imw.mWinAnimator.mAnimLayer = imw.mLayer + adj;
-            if (DEBUG_LAYERS) Slog.v(TAG_WM, "IM win " + imw
-                    + " anim layer: " + imw.mWinAnimator.mAnimLayer);
-            for (int i = imw.mChildWindows.size() - 1; i >= 0; i--) {
-                final WindowState childWindow = imw.mChildWindows.get(i);
-                childWindow.mWinAnimator.mAnimLayer = childWindow.mLayer + adj;
-                if (DEBUG_LAYERS) Slog.v(TAG_WM, "IM win " + childWindow
-                        + " anim layer: " + childWindow.mWinAnimator.mAnimLayer);
-            }
+            imw.adjustAnimLayer(adj);
         }
         for (int i = mService.mInputMethodDialogs.size() - 1; i >= 0; i--) {
             final WindowState dialog = mService.mInputMethodDialogs.get(i);
+            // TODO: This and other places setting mAnimLayer can probably use WS.adjustAnimLayer,
+            // but need to make sure we are not setting things twice for child windows that are
+            // already in the list.
             dialog.mWinAnimator.mAnimLayer = dialog.mLayer + adj;
             if (DEBUG_LAYERS) Slog.v(TAG_WM, "IM win " + imw
                     + " anim layer: " + dialog.mWinAnimator.mAnimLayer);
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 9c7e0f8..76189be 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -1146,14 +1146,9 @@
     private int indexOfWinInWindowList(WindowState targetWin, WindowList windows) {
         for (int i = windows.size() - 1; i >= 0; i--) {
             final WindowState w = windows.get(i);
-            if (w == targetWin) {
+            if (w == targetWin || w.hasChild(targetWin)) {
                 return i;
             }
-            if (!w.mChildWindows.isEmpty()) {
-                if (indexOfWinInWindowList(targetWin, w.mChildWindows) >= 0) {
-                    return i;
-                }
-            }
         }
         return -1;
     }
@@ -1646,30 +1641,6 @@
         moveInputMethodDialogsLocked(pos);
     }
 
-    private int tmpRemoveWindowLocked(int interestingPos, WindowState win) {
-        WindowList windows = win.getWindowList();
-        int wpos = windows.indexOf(win);
-        if (wpos >= 0) {
-            if (wpos < interestingPos) interestingPos--;
-            if (DEBUG_WINDOW_MOVEMENT) Slog.v(TAG_WM, "Temp removing at " + wpos + ": " + win);
-            windows.remove(wpos);
-            mWindowsChanged = true;
-            int NC = win.mChildWindows.size();
-            while (NC > 0) {
-                NC--;
-                WindowState cw = win.mChildWindows.get(NC);
-                int cpos = windows.indexOf(cw);
-                if (cpos >= 0) {
-                    if (cpos < interestingPos) interestingPos--;
-                    if (DEBUG_WINDOW_MOVEMENT) Slog.v(TAG_WM, "Temp removing child at "
-                            + cpos + ": " + cw);
-                    windows.remove(cpos);
-                }
-            }
-        }
-        return interestingPos;
-    }
-
     private void reAddWindowToListInOrderLocked(WindowState win) {
         addWindowToListInOrderLocked(win, false);
         // This is a hack to get all of the child windows added as well
@@ -1681,7 +1652,7 @@
             if (DEBUG_WINDOW_MOVEMENT) Slog.v(TAG_WM, "ReAdd removing from " + wpos + ": " + win);
             windows.remove(wpos);
             mWindowsChanged = true;
-            reAddWindowLocked(wpos, win);
+            win.reAddWindowLocked(wpos);
         }
     }
 
@@ -1701,7 +1672,7 @@
         final int N = dialogs.size();
         if (DEBUG_INPUT_METHOD) Slog.v(TAG_WM, "Removing " + N + " dialogs w/pos=" + pos);
         for (int i=0; i<N; i++) {
-            pos = tmpRemoveWindowLocked(pos, dialogs.get(i));
+            pos = dialogs.get(i).removeFromWindowList(pos);
         }
         if (DEBUG_INPUT_METHOD) {
             Slog.v(TAG_WM, "Window list w/pos=" + pos);
@@ -1725,7 +1696,7 @@
             for (int i=0; i<N; i++) {
                 WindowState win = dialogs.get(i);
                 win.mTargetAppToken = targetAppToken;
-                pos = reAddWindowLocked(pos, win);
+                pos = win.reAddWindowLocked(pos);
             }
             if (DEBUG_INPUT_METHOD) {
                 Slog.v(TAG_WM, "Final window list:");
@@ -1762,16 +1733,14 @@
             // First check to see if the input method windows are already
             // located here, and contiguous.
             final int N = windows.size();
-            WindowState firstImWin = imPos < N
-                    ? windows.get(imPos) : null;
+            final WindowState firstImWin = imPos < N ? windows.get(imPos) : null;
 
             // Figure out the actual input method window that should be
             // at the bottom of their stack.
-            WindowState baseImWin = imWin != null
-                    ? imWin : mInputMethodDialogs.get(0);
-            if (baseImWin.mChildWindows.size() > 0) {
-                WindowState cw = baseImWin.mChildWindows.get(0);
-                if (cw.mSubLayer < 0) baseImWin = cw;
+            WindowState baseImWin = imWin != null ? imWin : mInputMethodDialogs.get(0);
+            final WindowState cw = baseImWin.getBottomChild();
+            if (cw != null && cw.mSubLayer < 0) {
+                baseImWin = cw;
             }
 
             if (firstImWin == baseImWin) {
@@ -1807,13 +1776,13 @@
                     Slog.v(TAG_WM, "Moving IM from " + imPos);
                     logWindowList(windows, "  ");
                 }
-                imPos = tmpRemoveWindowLocked(imPos, imWin);
+                imPos = imWin.removeFromWindowList(imPos);
                 if (DEBUG_INPUT_METHOD) {
                     Slog.v(TAG_WM, "List after removing with new pos " + imPos + ":");
                     logWindowList(windows, "  ");
                 }
                 imWin.mTargetAppToken = mInputMethodTarget.mAppToken;
-                reAddWindowLocked(imPos, imWin);
+                imWin.reAddWindowLocked(imPos);
                 if (DEBUG_INPUT_METHOD) {
                     Slog.v(TAG_WM, "List after moving IM to " + imPos + ":");
                     logWindowList(windows, "  ");
@@ -1829,7 +1798,7 @@
 
             if (imWin != null) {
                 if (DEBUG_INPUT_METHOD) Slog.v(TAG_WM, "Moving IM from " + imPos);
-                tmpRemoveWindowLocked(0, imWin);
+                imWin.removeFromWindowList(0);
                 imWin.mTargetAppToken = null;
                 reAddWindowToListInOrderLocked(imWin);
                 if (DEBUG_INPUT_METHOD) {
@@ -1850,7 +1819,7 @@
         return true;
     }
 
-    private static boolean excludeWindowTypeFromTapOutTask(int windowType) {
+    static boolean excludeWindowTypeFromTapOutTask(int windowType) {
         switch (windowType) {
             case TYPE_STATUS_BAR:
             case TYPE_NAVIGATION_BAR:
@@ -2454,7 +2423,7 @@
             }
         }
 
-        removeWindowInnerLocked(win);
+        win.removeLocked();
         // Removing a visible window will effect the computed orientation
         // So just update orientation if needed.
         if (wasVisible && updateOrientationFromAppTokensLocked(false)) {
@@ -2464,41 +2433,12 @@
         Binder.restoreCallingIdentity(origId);
     }
 
-    void removeWindowInnerLocked(WindowState win) {
-        if (win.mRemoved) {
-            // Nothing to do.
-            if (DEBUG_ADD_REMOVE) Slog.v(TAG_WM,
-                    "removeWindowInnerLocked: " + win + " Already removed...");
-            return;
-        }
-
-        for (int i = win.mChildWindows.size() - 1; i >= 0; i--) {
-            WindowState cwin = win.mChildWindows.get(i);
-            Slog.w(TAG_WM, "Force-removing child win " + cwin + " from container " + win);
-            removeWindowInnerLocked(cwin);
-        }
-
-        win.mRemoved = true;
-
-        if (mInputMethodTarget == win) {
-            moveInputMethodWindowsIfNeededLocked(false);
-        }
-
-        if (false) {
-            RuntimeException e = new RuntimeException("here");
-            e.fillInStackTrace();
-            Slog.w(TAG_WM, "Removing window " + win, e);
-        }
-
-        final int type = win.mAttrs.type;
-        if (excludeWindowTypeFromTapOutTask(type)) {
-            final DisplayContent displaycontent = win.getDisplayContent();
-            displaycontent.mTapExcludedWindows.remove(win);
-        }
-        mPolicy.removeWindowLw(win);
-        win.removeLocked();
-
-        if (DEBUG_ADD_REMOVE) Slog.v(TAG_WM, "removeWindowInnerLocked: " + win);
+    /**
+     * Performs some centralized bookkeeping clean-up on the window that is being removed.
+     * NOTE: Should only be called from {@link WindowState#removeLocked()}
+     */
+    void postWindowRemoveCleanupLocked(WindowState win) {
+        if (DEBUG_ADD_REMOVE) Slog.v(TAG_WM, "postWindowRemoveCleanupLocked: " + win);
         mWindowMap.remove(win.mClient.asBinder());
         if (win.mAppOp != AppOpsManager.OP_NONE) {
             mAppOps.finishOp(win.mAppOp, win.getOwningUid(), win.getOwningPackage());
@@ -2551,7 +2491,7 @@
             }
         }
 
-        if (type == TYPE_WALLPAPER) {
+        if (win.mAttrs.type == TYPE_WALLPAPER) {
             mWallpaperControllerLocked.clearLastWallpaperTimeoutTime();
             getDefaultDisplayContentLocked().pendingLayoutChanges |=
                     WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
@@ -4377,7 +4317,7 @@
                 WindowState win = wtoken.allAppWindows.get(i);
                 if (win == wtoken.startingWindow) {
                     // Starting window that's exiting will be removed when the animation
-                    // finishes. Mark all relevant flags for that finishExit will proceed
+                    // finishes. Mark all relevant flags for that onExitAnimationDone will proceed
                     // all the way to actually remove it.
                     if (!visible && win.isVisibleNow() && wtoken.mAppAnimator.isAnimating()) {
                         win.mAnimatingExit = true;
@@ -4865,38 +4805,6 @@
         }
     }
 
-    private final int reAddWindowLocked(int index, WindowState win) {
-        final WindowList windows = win.getWindowList();
-        // Adding child windows relies on mChildWindows being ordered by mSubLayer.
-        final int NCW = win.mChildWindows.size();
-        boolean winAdded = false;
-        for (int j=0; j<NCW; j++) {
-            WindowState cwin = win.mChildWindows.get(j);
-            if (!winAdded && cwin.mSubLayer >= 0) {
-                if (DEBUG_WINDOW_MOVEMENT) Slog.v(TAG_WM, "Re-adding child window at "
-                        + index + ": " + cwin);
-                win.mRebuilding = false;
-                windows.add(index, win);
-                index++;
-                winAdded = true;
-            }
-            if (DEBUG_WINDOW_MOVEMENT) Slog.v(TAG_WM, "Re-adding window at "
-                    + index + ": " + cwin);
-            cwin.mRebuilding = false;
-            windows.add(index, cwin);
-            index++;
-        }
-        if (!winAdded) {
-            if (DEBUG_WINDOW_MOVEMENT) Slog.v(TAG_WM, "Re-adding window at "
-                    + index + ": " + win);
-            win.mRebuilding = false;
-            windows.add(index, win);
-            index++;
-        }
-        mWindowsChanged = true;
-        return index;
-    }
-
     private final int reAddAppWindowsLocked(final DisplayContent displayContent, int index,
                                             WindowToken token) {
         final int NW = token.windows.size();
@@ -4905,7 +4813,7 @@
             final DisplayContent winDisplayContent = win.getDisplayContent();
             if (winDisplayContent == displayContent || winDisplayContent == null) {
                 win.mDisplayContent = displayContent;
-                index = reAddWindowLocked(index, win);
+                index = win.reAddWindowLocked(index);
             }
         }
         return index;
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 5c40947..4dbac7e 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -49,6 +49,7 @@
 import android.view.InputEventReceiver;
 import android.view.View;
 import android.view.ViewTreeObserver;
+import android.view.WindowInfo;
 import android.view.WindowManager;
 import android.view.WindowManagerPolicy;
 
@@ -61,6 +62,7 @@
 import static android.app.ActivityManager.StackId.DOCKED_STACK_ID;
 import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
 import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
+import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_CONTENT;
 import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_FRAME;
 import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION;
@@ -100,6 +102,7 @@
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_APP_TRANSITIONS;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_CONFIGURATION;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_FOCUS_LIGHT;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_LAYERS;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_LAYOUT;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ORIENTATION;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_POWER;
@@ -107,6 +110,7 @@
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_STARTING_WINDOW;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_SURFACE_TRACE;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_VISIBILITY;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WINDOW_MOVEMENT;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
 import static com.android.server.wm.WindowStateAnimator.COMMIT_DRAW_PENDING;
@@ -157,7 +161,7 @@
     final WindowManager.LayoutParams mAttrs = new WindowManager.LayoutParams();
     final DeathRecipient mDeathRecipient;
     final WindowState mParentWindow;
-    final WindowList mChildWindows = new WindowList();
+    private final WindowList mChildWindows = new WindowList();
     final int mBaseLayer;
     final int mSubLayer;
     final boolean mLayoutAttached;
@@ -1454,6 +1458,31 @@
     }
 
     void removeLocked() {
+        if (mRemoved) {
+            // Nothing to do.
+            if (DEBUG_ADD_REMOVE) Slog.v(TAG_WM, "WS.removeLocked: " + this + " Already removed...");
+            return;
+        }
+
+        for (int i = mChildWindows.size() - 1; i >= 0; i--) {
+            final WindowState child = mChildWindows.get(i);
+            Slog.w(TAG_WM, "Force-removing child win " + child + " from container " + this);
+            child.removeLocked();
+        }
+
+        mRemoved = true;
+
+        if (mService.mInputMethodTarget == this) {
+            mService.moveInputMethodWindowsIfNeededLocked(false);
+        }
+
+        final int type = mAttrs.type;
+        if (WindowManagerService.excludeWindowTypeFromTapOutTask(type)) {
+            final DisplayContent displaycontent = getDisplayContent();
+            displaycontent.mTapExcludedWindows.remove(this);
+        }
+        mPolicy.removeWindowLw(this);
+
         disposeInputChannel();
 
         if (isChildWindow()) {
@@ -1469,6 +1498,8 @@
             // Ignore if it has already been removed (usually because
             // we are doing this as part of processing a death note.)
         }
+
+        mService.postWindowRemoveCleanupLocked(this);
     }
 
     void setHasSurface(boolean hasSurface) {
@@ -1630,7 +1661,7 @@
                 win.mReplacingWindow = null;
                 mSkipEnterAnimationForSeamlessReplacement = false;
                 if (win.mAnimatingExit || !animateReplacingWindow) {
-                    mService.removeWindowInnerLocked(win);
+                    win.removeLocked();
                 }
             }
         }
@@ -2830,6 +2861,24 @@
         return mParentWindow != null;
     }
 
+    boolean hasChild(WindowState child) {
+        for (int i = mChildWindows.size() - 1; i >= 0; --i) {
+            if (mChildWindows.get(i) == child) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Returns the bottom child window in regards to z-order of this window or null if no children.
+     */
+    WindowState getBottomChild() {
+        // Child windows are z-ordered based on sub-layer using {@link #sWindowSubLayerComparator}
+        // and the child with the lowest z-order will be at the head of the list.
+        return mChildWindows.isEmpty() ? null : mChildWindows.get(0);
+    }
+
     boolean layoutInParentFrame() {
         return isChildWindow() && (mAttrs.privateFlags & PRIVATE_FLAG_LAYOUT_CHILD_WINDOW_IN_PARENT_FRAME) != 0;
     }
@@ -2983,4 +3032,170 @@
                     + " Callers=" + Debug.getCallers(4));
         }
     }
+
+    WindowInfo getWindowInfo() {
+        WindowInfo windowInfo = WindowInfo.obtain();
+        windowInfo.type = mAttrs.type;
+        windowInfo.layer = mLayer;
+        windowInfo.token = mClient.asBinder();
+        windowInfo.title = mAttrs.accessibilityTitle;
+        windowInfo.accessibilityIdOfAnchor = mAttrs.accessibilityIdOfAnchor;
+        windowInfo.focused = isFocused();
+
+        if (isChildWindow()) {
+            windowInfo.parentToken = mParentWindow.mClient.asBinder();
+        }
+
+        final int childCount = mChildWindows.size();
+        if (childCount > 0) {
+            if (windowInfo.childTokens == null) {
+                windowInfo.childTokens = new ArrayList<>();
+            }
+            for (int j = 0; j < childCount; j++) {
+                final WindowState child = mChildWindows.get(j);
+                windowInfo.childTokens.add(child.mClient.asBinder());
+            }
+        }
+
+        return windowInfo;
+    }
+
+    void adjustAnimLayer(int adj) {
+        mWinAnimator.mAnimLayer = mLayer + adj;
+        if (DEBUG_LAYERS) Slog.v(TAG_WM, "win=" + this + " anim layer: " + mWinAnimator.mAnimLayer);
+        for (int i = mChildWindows.size() - 1; i >= 0; i--) {
+            final WindowState child = mChildWindows.get(i);
+            child.adjustAnimLayer(adj);
+        }
+    }
+
+    // TODO: come-up with a better name for this method that represents what it does.
+    // Or, it is probably not going to matter anyways if we are successful in getting rid of
+    // the WindowList concept.
+    int reAddWindowLocked(int index) {
+        final WindowList windows = getWindowList();
+        // Adding child windows relies on child windows being ordered by mSubLayer using
+        // {@link #sWindowSubLayerComparator}.
+        final int childCount = mChildWindows.size();
+        boolean winAdded = false;
+        for (int j = 0; j < childCount; j++) {
+            WindowState child = mChildWindows.get(j);
+            if (!winAdded && child.mSubLayer >= 0) {
+                if (DEBUG_WINDOW_MOVEMENT) Slog.v(TAG_WM,
+                        "Re-adding child window at " + index + ": " + child);
+                mRebuilding = false;
+                windows.add(index, this);
+                index++;
+                winAdded = true;
+            }
+            if (DEBUG_WINDOW_MOVEMENT) Slog.v(TAG_WM, "Re-adding window at " + index + ": " + child);
+            child.mRebuilding = false;
+            windows.add(index, child);
+            index++;
+        }
+        if (!winAdded) {
+            if (DEBUG_WINDOW_MOVEMENT) Slog.v(TAG_WM, "Re-adding window at " + index + ": " + this);
+            mRebuilding = false;
+            windows.add(index, this);
+            index++;
+        }
+        mService.mWindowsChanged = true;
+        return index;
+    }
+
+    int removeFromWindowList(int interestingPos) {
+        final WindowList windows = getWindowList();
+        int wpos = windows.indexOf(this);
+        if (wpos < 0) {
+            return interestingPos;
+        }
+
+        if (wpos < interestingPos) interestingPos--;
+        if (DEBUG_WINDOW_MOVEMENT) Slog.v(TAG_WM, "Temp removing at " + wpos + ": " + this);
+        windows.remove(wpos);
+        mService.mWindowsChanged = true;
+        int childCount = mChildWindows.size();
+        while (childCount > 0) {
+            childCount--;
+            final WindowState cw = mChildWindows.get(childCount);
+            int cpos = windows.indexOf(cw);
+            if (cpos >= 0) {
+                if (cpos < interestingPos) interestingPos--;
+                if (DEBUG_WINDOW_MOVEMENT) Slog.v(TAG_WM,
+                        "Temp removing child at " + cpos + ": " + cw);
+                windows.remove(cpos);
+            }
+        }
+        return interestingPos;
+    }
+
+    void onExitAnimationDone() {
+        if (DEBUG_ANIM) Slog.v(TAG, "onExitAnimationDone in " + this
+                + ": exiting=" + mAnimatingExit + " remove=" + mRemoveOnExit
+                + " windowAnimating=" + mWinAnimator.isWindowAnimationSet());
+
+        if (!mChildWindows.isEmpty()) {
+            // Copying to a different list as multiple children can be removed.
+            final WindowList childWindows = new WindowList(mChildWindows);
+            for (int i = childWindows.size() - 1; i >= 0; i--) {
+                childWindows.get(i).onExitAnimationDone();
+            }
+        }
+
+        if (mWinAnimator.mEnteringAnimation) {
+            mWinAnimator.mEnteringAnimation = false;
+            mService.requestTraversal();
+            // System windows don't have an activity and an app token as a result, but need a way
+            // to be informed about their entrance animation end.
+            if (mAppToken == null) {
+                try {
+                    mClient.dispatchWindowShown();
+                } catch (RemoteException e) {
+                }
+            }
+        }
+
+        if (!mWinAnimator.isWindowAnimationSet()) {
+            //TODO (multidisplay): Accessibility is supported only for the default display.
+            if (mService.mAccessibilityController != null && getDisplayId() == DEFAULT_DISPLAY) {
+                mService.mAccessibilityController.onSomeWindowResizedOrMovedLocked();
+            }
+        }
+
+        if (!mAnimatingExit) {
+            return;
+        }
+
+        if (mWinAnimator.isWindowAnimationSet()) {
+            return;
+        }
+
+        if (WindowManagerService.localLOGV || DEBUG_ADD_REMOVE) Slog.v(TAG,
+                "Exit animation finished in " + this + ": remove=" + mRemoveOnExit);
+
+        mDestroying = true;
+
+        final boolean hasSurface = mWinAnimator.hasSurface();
+        if (hasSurface) {
+            mWinAnimator.hide("onExitAnimationDone");
+        }
+
+        // If we have an app token, we ask it to destroy the surface for us, so that it can take
+        // care to ensure the activity has actually stopped and the surface is not still in use.
+        // Otherwise we add the service to mDestroySurface and allow it to be processed in our next
+        // transaction.
+        if (mAppToken != null) {
+            mAppToken.destroySurfaces();
+        } else {
+            if (hasSurface) {
+                mService.mDestroySurface.add(this);
+            }
+            if (mRemoveOnExit) {
+                mService.mPendingRemove.add(this);
+                mRemoveOnExit = false;
+            }
+        }
+        mAnimatingExit = false;
+        mService.mWallpaperControllerLocked.hideWallpapers(this);
+    }
 }
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index 9d3a221..673d28c 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -24,7 +24,6 @@
 import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
 import static com.android.server.wm.AppWindowAnimator.sDummyAnimation;
 import static com.android.server.wm.DragResizeMode.DRAG_RESIZE_MODE_FREEFORM;
-import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ADD_REMOVE;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ANIM;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_LAYERS;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_LAYOUT_REPEATS;
@@ -53,7 +52,6 @@
 import android.graphics.RectF;
 import android.graphics.Region;
 import android.os.Debug;
-import android.os.RemoteException;
 import android.os.Trace;
 import android.util.Slog;
 import android.view.DisplayInfo;
@@ -102,7 +100,7 @@
     // Unchanging local convenience fields.
     final WindowManagerService mService;
     final WindowState mWin;
-    final WindowStateAnimator mAttachedWinAnimator;
+    private final WindowStateAnimator mParentWinAnimator;
     final WindowAnimator mAnimator;
     AppWindowAnimator mAppAnimator;
     final Session mSession;
@@ -260,8 +258,7 @@
         }
 
         mWin = win;
-        mAttachedWinAnimator = !win.isChildWindow()
-                ? null : win.mParentWindow.mWinAnimator;
+        mParentWinAnimator = !win.isChildWindow() ? null : win.mParentWindow.mWinAnimator;
         mAppAnimator = win.mAppToken == null ? null : win.mAppToken.mAppAnimator;
         mSession = win.mSession;
         mAttrType = win.mAttrs.type;
@@ -309,7 +306,7 @@
      */
     boolean isAnimationSet() {
         return mAnimation != null
-                || (mAttachedWinAnimator != null && mAttachedWinAnimator.mAnimation != null)
+                || (mParentWinAnimator != null && mParentWinAnimator.mAnimation != null)
                 || (mAppAnimator != null && mAppAnimator.isAnimating());
     }
 
@@ -490,7 +487,7 @@
             }
         }
 
-        finishExit();
+        mWin.onExitAnimationDone();
         final int displayId = mWin.getDisplayId();
         mAnimator.setPendingLayoutChanges(displayId, WindowManagerPolicy.FINISH_LAYOUT_REDO_ANIM);
         if (DEBUG_LAYOUT_REPEATS)
@@ -504,80 +501,6 @@
         return false;
     }
 
-    void finishExit() {
-        if (DEBUG_ANIM) Slog.v(
-                TAG, "finishExit in " + this
-                + ": exiting=" + mWin.mAnimatingExit
-                + " remove=" + mWin.mRemoveOnExit
-                + " windowAnimating=" + isWindowAnimationSet());
-
-        if (!mWin.mChildWindows.isEmpty()) {
-            // Copying to a different list as multiple children can be removed.
-            final WindowList childWindows = new WindowList(mWin.mChildWindows);
-            for (int i = childWindows.size() - 1; i >= 0; i--) {
-                childWindows.get(i).mWinAnimator.finishExit();
-            }
-        }
-
-        if (mEnteringAnimation) {
-            mEnteringAnimation = false;
-            mService.requestTraversal();
-            // System windows don't have an activity and an app token as a result, but need a way
-            // to be informed about their entrance animation end.
-            if (mWin.mAppToken == null) {
-                try {
-                    mWin.mClient.dispatchWindowShown();
-                } catch (RemoteException e) {
-                }
-            }
-        }
-
-        if (!isWindowAnimationSet()) {
-            //TODO (multidisplay): Accessibility is supported only for the default display.
-            if (mService.mAccessibilityController != null
-                    && mWin.getDisplayId() == DEFAULT_DISPLAY) {
-                mService.mAccessibilityController.onSomeWindowResizedOrMovedLocked();
-            }
-        }
-
-        if (!mWin.mAnimatingExit) {
-            return;
-        }
-
-        if (isWindowAnimationSet()) {
-            return;
-        }
-
-        if (WindowManagerService.localLOGV || DEBUG_ADD_REMOVE) Slog.v(TAG,
-                "Exit animation finished in " + this + ": remove=" + mWin.mRemoveOnExit);
-
-
-        mWin.mDestroying = true;
-
-        final boolean hasSurface = hasSurface();
-        if (hasSurface) {
-            hide("finishExit");
-        }
-
-        // If we have an app token, we ask it to destroy the surface for us,
-        // so that it can take care to ensure the activity has actually stopped
-        // and the surface is not still in use. Otherwise we add the service to
-        // mDestroySurface and allow it to be processed in our next transaction.
-        if (mWin.mAppToken != null) {
-            mWin.mAppToken.destroySurfaces();
-        } else {
-            if (hasSurface) {
-                mService.mDestroySurface.add(mWin);
-            }
-            if (mWin.mRemoveOnExit) {
-                mService.mPendingRemove.add(mWin);
-                mWin.mRemoveOnExit = false;
-            }
-        }
-        mWin.mAnimatingExit = false;
-        mWallpaperControllerLocked.hideWallpapers(mWin);
-    }
-
     void hide(String reason) {
         if (!mLastHidden) {
             //dump();
@@ -925,8 +848,8 @@
     void computeShownFrameLocked() {
         final boolean selfTransformation = mHasLocalTransformation;
         Transformation attachedTransformation =
-                (mAttachedWinAnimator != null && mAttachedWinAnimator.mHasLocalTransformation)
-                ? mAttachedWinAnimator.mTransformation : null;
+                (mParentWinAnimator != null && mParentWinAnimator.mHasLocalTransformation)
+                ? mParentWinAnimator.mTransformation : null;
         Transformation appTransformation = (mAppAnimator != null && mAppAnimator.hasTransformation)
                 ? mAppAnimator.transformation : null;
 
diff --git a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
index 7cf08d2..1b9eeb0 100644
--- a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
+++ b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
@@ -213,9 +213,9 @@
             recoveringMemory = true;
             // Wait a little bit for things to settle down, and off we go.
             while (!mService.mForceRemoves.isEmpty()) {
-                WindowState ws = mService.mForceRemoves.remove(0);
+                final WindowState ws = mService.mForceRemoves.remove(0);
                 Slog.i(TAG, "Force removing: " + ws);
-                mService.removeWindowInnerLocked(ws);
+                ws.removeLocked();
             }
             Slog.w(TAG, "Due to memory failure, waiting a bit for next layout");
             Object tmp = new Object();
@@ -544,8 +544,8 @@
             mService.mPendingRemove.clear();
             DisplayContentList displayList = new DisplayContentList();
             for (i = 0; i < N; i++) {
-                WindowState w = mService.mPendingRemoveTmp[i];
-                mService.removeWindowInnerLocked(w);
+                final WindowState w = mService.mPendingRemoveTmp[i];
+                w.removeLocked();
                 final DisplayContent displayContent = w.getDisplayContent();
                 if (displayContent != null && !displayList.contains(displayContent)) {
                     displayList.add(displayContent);
@@ -1269,7 +1269,7 @@
                     // We also don't clear the mAnimatingExit flag for windows which have the
                     // mRemoveOnExit flag. This indicates an explicit remove request has been issued
                     // by the client. We should let animation proceed and not clear this flag or
-                    // they won't eventually be removed by WindowStateAnimator#finishExit.
+                    // they won't eventually be removed by WindowState#onExitAnimationDone.
                     if (!win.mWillReplaceWindow && !win.mRemoveOnExit) {
                         win.mAnimatingExit = false;
                         // Clear mAnimating flag together with mAnimatingExit. When animation
diff --git a/services/tests/servicestests/src/com/android/server/wm/TestWindowManagerPolicy.java b/services/tests/servicestests/src/com/android/server/wm/TestWindowManagerPolicy.java
index 3bb797e..936949e 100644
--- a/services/tests/servicestests/src/com/android/server/wm/TestWindowManagerPolicy.java
+++ b/services/tests/servicestests/src/com/android/server/wm/TestWindowManagerPolicy.java
@@ -19,17 +19,16 @@
 import com.android.internal.policy.IShortcutService;
 
 import android.content.Context;
-import android.content.pm.ActivityInfo;
 import android.content.res.CompatibilityInfo;
 import android.content.res.Configuration;
 import android.graphics.Rect;
 import android.os.Bundle;
 import android.os.IBinder;
 import android.os.RemoteException;
+import android.util.Log;
 import android.view.Display;
 import android.view.IWindowManager;
 import android.view.KeyEvent;
-import android.view.Surface;
 import android.view.View;
 import android.view.WindowManager;
 import android.view.WindowManagerPolicy;
@@ -37,7 +36,51 @@
 
 import java.io.PrintWriter;
 
+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_ACCESSIBILITY_OVERLAY;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_ABOVE_SUB_PANEL;
+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_APPLICATION_MEDIA_OVERLAY;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_PANEL;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL;
+import static android.view.WindowManager.LayoutParams.TYPE_BOOT_PROGRESS;
+import static android.view.WindowManager.LayoutParams.TYPE_DISPLAY_OVERLAY;
+import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER;
+import static android.view.WindowManager.LayoutParams.TYPE_DRAG;
+import static android.view.WindowManager.LayoutParams.TYPE_DREAM;
+import static android.view.WindowManager.LayoutParams.TYPE_INPUT_CONSUMER;
+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_KEYGUARD_DIALOG;
+import static android.view.WindowManager.LayoutParams.TYPE_KEYGUARD_SCRIM;
+import static android.view.WindowManager.LayoutParams.TYPE_MAGNIFICATION_OVERLAY;
+import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR;
+import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL;
+import static android.view.WindowManager.LayoutParams.TYPE_PHONE;
+import static android.view.WindowManager.LayoutParams.TYPE_POINTER;
+import static android.view.WindowManager.LayoutParams.TYPE_PRIORITY_PHONE;
+import static android.view.WindowManager.LayoutParams.TYPE_PRIVATE_PRESENTATION;
+import static android.view.WindowManager.LayoutParams.TYPE_QS_DIALOG;
+import static android.view.WindowManager.LayoutParams.TYPE_SCREENSHOT;
+import static android.view.WindowManager.LayoutParams.TYPE_SEARCH_BAR;
+import static android.view.WindowManager.LayoutParams.TYPE_SECURE_SYSTEM_OVERLAY;
+import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR;
+import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL;
+import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL;
+import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_ALERT;
+import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG;
+import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_ERROR;
+import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY;
+import static android.view.WindowManager.LayoutParams.TYPE_TOAST;
+import static android.view.WindowManager.LayoutParams.TYPE_VOICE_INTERACTION;
+import static android.view.WindowManager.LayoutParams.TYPE_VOICE_INTERACTION_STARTING;
+import static android.view.WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY;
+import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
+
 public class TestWindowManagerPolicy implements WindowManagerPolicy {
+    private static final String TAG = "TestWindowManagerPolicy";
 
     @Override
     public void registerShortcutKey(long shortcutCode, IShortcutService shortcutKeyReceiver)
@@ -89,11 +132,124 @@
 
     @Override
     public int windowTypeToLayerLw(int type) {
-        return 0;
+        // TODO: figure-out a good way to keep this in-sync with PhoneWindowManager...sigh!
+        if (type >= FIRST_APPLICATION_WINDOW && type <= LAST_APPLICATION_WINDOW) {
+            return 2;
+        }
+        switch (type) {
+            case TYPE_PRIVATE_PRESENTATION:
+                return 2;
+            case TYPE_WALLPAPER:
+                // wallpaper is at the bottom, though the window manager may move it.
+                return 2;
+            case TYPE_DOCK_DIVIDER:
+                return 2;
+            case TYPE_QS_DIALOG:
+                return 2;
+            case TYPE_PHONE:
+                return 3;
+            case TYPE_SEARCH_BAR:
+            case TYPE_VOICE_INTERACTION_STARTING:
+                return 4;
+            case TYPE_VOICE_INTERACTION:
+                // voice interaction layer is almost immediately above apps.
+                return 5;
+            case TYPE_INPUT_CONSUMER:
+                return 6;
+            case TYPE_SYSTEM_DIALOG:
+                return 7;
+            case TYPE_TOAST:
+                // toasts and the plugged-in battery thing
+                return 8;
+            case TYPE_PRIORITY_PHONE:
+                // SIM errors and unlock.  Not sure if this really should be in a high layer.
+                return 9;
+            case TYPE_DREAM:
+                // used for Dreams (screensavers with TYPE_DREAM windows)
+                return 10;
+            case TYPE_SYSTEM_ALERT:
+                // like the ANR / app crashed dialogs
+                return 11;
+            case TYPE_INPUT_METHOD:
+                // on-screen keyboards and other such input method user interfaces go here.
+                return 12;
+            case TYPE_INPUT_METHOD_DIALOG:
+                // on-screen keyboards and other such input method user interfaces go here.
+                return 13;
+            case TYPE_KEYGUARD_SCRIM:
+                // the safety window that shows behind keyguard while keyguard is starting
+                return 14;
+            case TYPE_STATUS_BAR_SUB_PANEL:
+                return 15;
+            case TYPE_STATUS_BAR:
+                return 16;
+            case TYPE_STATUS_BAR_PANEL:
+                return 17;
+            case TYPE_KEYGUARD_DIALOG:
+                return 18;
+            case TYPE_VOLUME_OVERLAY:
+                // the on-screen volume indicator and controller shown when the user
+                // changes the device volume
+                return 19;
+            case TYPE_SYSTEM_OVERLAY:
+                // the on-screen volume indicator and controller shown when the user
+                // changes the device volume
+                return 20;
+            case TYPE_NAVIGATION_BAR:
+                // the navigation bar, if available, shows atop most things
+                return 21;
+            case TYPE_NAVIGATION_BAR_PANEL:
+                // some panels (e.g. search) need to show on top of the navigation bar
+                return 22;
+            case TYPE_SCREENSHOT:
+                // screenshot selection layer shouldn't go above system error, but it should cover
+                // navigation bars at the very least.
+                return 23;
+            case TYPE_SYSTEM_ERROR:
+                // system-level error dialogs
+                return 24;
+            case TYPE_MAGNIFICATION_OVERLAY:
+                // used to highlight the magnified portion of a display
+                return 25;
+            case TYPE_DISPLAY_OVERLAY:
+                // used to simulate secondary display devices
+                return 26;
+            case TYPE_DRAG:
+                // the drag layer: input for drag-and-drop is associated with this window,
+                // which sits above all other focusable windows
+                return 27;
+            case TYPE_ACCESSIBILITY_OVERLAY:
+                // overlay put by accessibility services to intercept user interaction
+                return 28;
+            case TYPE_SECURE_SYSTEM_OVERLAY:
+                return 29;
+            case TYPE_BOOT_PROGRESS:
+                return 30;
+            case TYPE_POINTER:
+                // the (mouse) pointer layer
+                return 31;
+        }
+        Log.e(TAG, "Unknown window type: " + type);
+        return 2;
     }
 
     @Override
     public int subWindowTypeToLayerLw(int type) {
+        // TODO: figure-out a good way to keep this in-sync with PhoneWindowManager...
+        switch (type) {
+            case TYPE_APPLICATION_PANEL:
+            case TYPE_APPLICATION_ATTACHED_DIALOG:
+                return 1;
+            case TYPE_APPLICATION_MEDIA:
+                return -2;
+            case TYPE_APPLICATION_MEDIA_OVERLAY:
+                return -1;
+            case TYPE_APPLICATION_SUB_PANEL:
+                return 2;
+            case TYPE_APPLICATION_ABOVE_SUB_PANEL:
+                return 3;
+        }
+        Log.e(TAG, "Unknown sub-window type: " + type);
         return 0;
     }
 
diff --git a/services/tests/servicestests/src/com/android/server/wm/WindowStateTests.java b/services/tests/servicestests/src/com/android/server/wm/WindowStateTests.java
index b66a72a..68c64f7 100644
--- a/services/tests/servicestests/src/com/android/server/wm/WindowStateTests.java
+++ b/services/tests/servicestests/src/com/android/server/wm/WindowStateTests.java
@@ -16,14 +16,20 @@
 
 package com.android.server.wm;
 
+import com.android.server.LocalServices;
+
 import android.content.Context;
 import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
 import android.view.IWindow;
 import android.view.WindowManager;
 import android.view.WindowManagerPolicy;
 
 import static android.view.WindowManager.LayoutParams.FIRST_SUB_WINDOW;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA_OVERLAY;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_PANEL;
 
 /**
  * Tests for the {@link WindowState} class.
@@ -32,9 +38,10 @@
  * Install: adb install -r out/target/product/angler/data/app/FrameworksServicesTests/FrameworksServicesTests.apk
  * Run: adb shell am instrument -w -e class com.android.server.wm.WindowStateTests com.android.frameworks.servicestests/android.support.test.runner.AndroidJUnitRunner
  */
+@SmallTest
 public class WindowStateTests extends AndroidTestCase {
 
-    private WindowManagerService mWm;
+    private static WindowManagerService sWm = null;
     private WindowToken mWindowToken;
     private final WindowManagerPolicy mPolicy = new TestWindowManagerPolicy();
     private final IWindow mIWindow = new TestIWindow();
@@ -43,14 +50,18 @@
     public void setUp() throws Exception {
         final Context context = getContext();
 //        final InputManagerService im = new InputManagerService(context);
-        mWm = WindowManagerService.main(context, /*im*/ null, true, false, false, mPolicy);
-        mWindowToken = new WindowToken(mWm, null, 0, false);
+        if (sWm == null) {
+            // We only want to do this once for the test process as we don't want WM to try to
+            // register a bunch of local services again.
+            sWm = WindowManagerService.main(context, /*im*/ null, true, false, false, mPolicy);
+        }
+        mWindowToken = new WindowToken(sWm, null, 0, false);
     }
 
     public void testIsParentWindowHidden() throws Exception {
-        final WindowState parentWindow = createWindow(null);
-        final WindowState child1 = createWindow(parentWindow);
-        final WindowState child2 = createWindow(parentWindow);
+        final WindowState parentWindow = createWindow(null, TYPE_APPLICATION);
+        final WindowState child1 = createWindow(parentWindow, FIRST_SUB_WINDOW);
+        final WindowState child2 = createWindow(parentWindow, FIRST_SUB_WINDOW);
 
         assertFalse(parentWindow.mHidden);
         assertFalse(parentWindow.isParentWindowHidden());
@@ -63,11 +74,65 @@
         assertTrue(child2.isParentWindowHidden());
     }
 
-    private WindowState createWindow(WindowState parent) {
-        final int type = (parent == null) ? TYPE_APPLICATION : FIRST_SUB_WINDOW;
+    public void testIsChildWindow() throws Exception {
+        final WindowState parentWindow = createWindow(null, TYPE_APPLICATION);
+        final WindowState child1 = createWindow(parentWindow, FIRST_SUB_WINDOW);
+        final WindowState child2 = createWindow(parentWindow, FIRST_SUB_WINDOW);
+        final WindowState randomWindow = createWindow(null, TYPE_APPLICATION);
+
+        assertFalse(parentWindow.isChildWindow());
+        assertTrue(child1.isChildWindow());
+        assertTrue(child2.isChildWindow());
+        assertFalse(randomWindow.isChildWindow());
+    }
+
+    public void testHasChild() throws Exception {
+        final WindowState win1 = createWindow(null, TYPE_APPLICATION);
+        final WindowState win11 = createWindow(win1, FIRST_SUB_WINDOW);
+        final WindowState win12 = createWindow(win1, FIRST_SUB_WINDOW);
+        final WindowState win2 = createWindow(null, TYPE_APPLICATION);
+        final WindowState win21 = createWindow(win2, FIRST_SUB_WINDOW);
+        final WindowState randomWindow = createWindow(null, TYPE_APPLICATION);
+
+        assertTrue(win1.hasChild(win11));
+        assertTrue(win1.hasChild(win12));
+        assertTrue(win2.hasChild(win21));
+
+        assertFalse(win1.hasChild(win21));
+        assertFalse(win1.hasChild(randomWindow));
+
+        assertFalse(win2.hasChild(win11));
+        assertFalse(win2.hasChild(win12));
+        assertFalse(win2.hasChild(randomWindow));
+    }
+
+    public void testGetBottomChild() throws Exception {
+        final WindowState parentWindow = createWindow(null, TYPE_APPLICATION);
+        assertNull(parentWindow.getBottomChild());
+
+        final WindowState child1 = createWindow(parentWindow, TYPE_APPLICATION_PANEL);
+        assertEquals(child1, parentWindow.getBottomChild());
+
+        final WindowState child2 = createWindow(parentWindow, TYPE_APPLICATION_PANEL);
+        // Since child1 and child2 are at the same layer, then child2 is expect to be added on top
+        // on child1
+        assertEquals(child1, parentWindow.getBottomChild());
+
+        final WindowState child3 = createWindow(parentWindow, TYPE_APPLICATION_MEDIA_OVERLAY);
+        // Since child3 is a negative layer, we would expect it to be added below current children
+        // with positive layers.
+        assertEquals(child3, parentWindow.getBottomChild());
+
+        final WindowState child4 = createWindow(parentWindow, TYPE_APPLICATION_MEDIA_OVERLAY);
+        // We would also expect additional negative layers to be added below existing negative
+        // layers.
+        assertEquals(child4, parentWindow.getBottomChild());
+    }
+
+    private WindowState createWindow(WindowState parent, int type) {
         final WindowManager.LayoutParams attrs = new WindowManager.LayoutParams(type);
 
-        return new WindowState(mWm, null, mIWindow, mWindowToken, parent, 0, 0, attrs, 0,
-                mWm.getDefaultDisplayContentLocked(), 0);
+        return new WindowState(sWm, null, mIWindow, mWindowToken, parent, 0, 0, attrs, 0,
+                sWm.getDefaultDisplayContentLocked(), 0);
     }
 }