WM: Introduce DisplayArea (3/n)

Introduces the concept of a DisplayArea, corresponding to an area
on a logical display within which content can be placed.

DisplayAreas can contain more DisplayAreas, WindowTokens and ActivityStacks.

A future CL will expose an API which allows leashing a DisplayArea to transform
all its content simultaneously.

DisplayAreas are managed by a DisplayAreaPolicy, which creates and places the
areas, and decides which area a container is placed in.

This CL introduces the concept, but applies a no-op policy that maintains the
current orderings and does not apply any overrides.

Future work that remains:
- Writing the feature policies
- Adjusting Display & DisplayMetrics if the area is constraining its children.
- Moving the policy into an product-adjustable component

Bug: 147406652
Test: atest WmTests
Change-Id: If6fb1bba3b65ebf7ac9fdf99408c54bf77f602c9
diff --git a/services/core/java/com/android/server/wm/DisplayArea.java b/services/core/java/com/android/server/wm/DisplayArea.java
new file mode 100644
index 0000000..b3edc91
--- /dev/null
+++ b/services/core/java/com/android/server/wm/DisplayArea.java
@@ -0,0 +1,265 @@
+/*
+ * Copyright (C) 2020 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 static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_BEHIND;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
+import static android.view.WindowManager.TRANSIT_KEYGUARD_UNOCCLUDE;
+import static android.view.WindowManagerPolicyConstants.APPLICATION_LAYER;
+
+import static com.android.internal.util.Preconditions.checkState;
+import static com.android.server.wm.DisplayAreaChildProto.DISPLAY_AREA;
+import static com.android.server.wm.DisplayAreaChildProto.UNKNOWN;
+import static com.android.server.wm.DisplayAreaChildProto.WINDOW;
+import static com.android.server.wm.DisplayAreaProto.CHILDREN;
+import static com.android.server.wm.DisplayAreaProto.NAME;
+import static com.android.server.wm.DisplayAreaProto.WINDOW_CONTAINER;
+import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_ORIENTATION;
+
+import android.graphics.Rect;
+import android.util.proto.ProtoOutputStream;
+
+import com.android.server.policy.WindowManagerPolicy;
+import com.android.server.protolog.common.ProtoLog;
+
+import java.util.Comparator;
+import java.util.function.Predicate;
+
+/**
+ * Container for grouping WindowContainer below DisplayContent.
+ *
+ * DisplayAreas are managed by a {@link DisplayAreaPolicy}, and can override configurations and
+ * can be leashed.
+ *
+ * DisplayAreas can contain nested DisplayAreas.
+ *
+ * DisplayAreas come in three flavors, to ensure that windows have the right Z-Order:
+ * - BELOW_TASKS: Can only contain BELOW_TASK DisplayAreas and WindowTokens that go below tasks.
+ * - ABOVE_TASKS: Can only contain ABOVE_TASK DisplayAreas and WindowTokens that go above tasks.
+ * - ANY: Can contain any kind of DisplayArea, and any kind of WindowToken or the Task container.
+ *        Cannot have a sibling that is of type ANY.
+ *
+ * @param <T> type of the children of the DisplayArea.
+ */
+public class DisplayArea<T extends WindowContainer> extends WindowContainer<T> {
+
+    protected final Type mType;
+    private final String mName;
+
+    DisplayArea(WindowManagerService wms, Type type, String name) {
+        super(wms);
+        // TODO(display-area): move this up to ConfigurationContainer
+        mOrientation = SCREEN_ORIENTATION_UNSET;
+        mType = type;
+        mName = name;
+    }
+
+    @Override
+    void onChildPositionChanged(WindowContainer child) {
+        super.onChildPositionChanged(child);
+
+        // Verify that we have proper ordering
+        Type.checkChild(mType, Type.typeOf(child));
+
+        if (child instanceof ActivityStack) {
+            // TODO(display-area): ActivityStacks are type ANY, but are allowed to have siblings.
+            //                     They might need a separate type.
+            return;
+        }
+
+        for (int i = 1; i < getChildCount(); i++) {
+            final WindowContainer top = getChildAt(i - 1);
+            final WindowContainer bottom = getChildAt(i);
+            if (child == top || child == bottom) {
+                Type.checkSiblings(Type.typeOf(top), Type.typeOf(bottom));
+            }
+        }
+    }
+
+    @Override
+    boolean fillsParent() {
+        return true;
+    }
+
+    @Override
+    String getName() {
+        return mName;
+    }
+
+    @Override
+    public final void dumpDebug(ProtoOutputStream proto, long fieldId, int logLevel) {
+        final long token = proto.start(fieldId);
+        super.dumpDebug(proto, WINDOW_CONTAINER, logLevel);
+        proto.write(NAME, mName);
+        for (int i = 0; i < getChildCount(); i++) {
+            final long childToken = proto.start(CHILDREN);
+            final T child = getChildAt(i);
+            if (child instanceof ActivityStack) {
+                // TODO(display-area): Dump stacks & tasks here, instead of in DisplayContent's
+                //  dumpDebug. For now, skip them here to avoid dumping them as UNKNOWN.
+            } else if (child instanceof WindowToken) {
+                ((WindowToken) child).dumpDebug(proto, WINDOW, logLevel);
+            } else if (child instanceof DisplayArea) {
+                child.dumpDebug(proto, DISPLAY_AREA, logLevel);
+            } else {
+                proto.write(UNKNOWN, child.getClass().getSimpleName());
+            }
+            proto.end(childToken);
+        }
+        proto.end(token);
+    }
+
+    /**
+     * DisplayArea that contains WindowTokens, and orders them according to their type.
+     */
+    public static class Tokens extends DisplayArea<WindowToken> {
+        int mLastKeyguardForcedOrientation = SCREEN_ORIENTATION_UNSPECIFIED;
+
+        private final Comparator<WindowToken> mWindowComparator =
+                Comparator.comparingInt(WindowToken::getWindowLayerFromType);
+
+        private final Predicate<WindowState> mGetOrientingWindow = w -> {
+            final WindowManagerPolicy policy = mWmService.mPolicy;
+            if (policy.isKeyguardHostWindow(w.mAttrs)) {
+                if (mWmService.mKeyguardGoingAway) {
+                    return false;
+                }
+                // Consider unoccluding only when all unknown visibilities have been
+                // resolved, as otherwise we just may be starting another occluding activity.
+                final boolean isUnoccluding =
+                        mDisplayContent.mAppTransition.getAppTransition()
+                                == TRANSIT_KEYGUARD_UNOCCLUDE
+                                && mDisplayContent.mUnknownAppVisibilityController.allResolved();
+                // If keyguard is showing, or we're unoccluding, force the keyguard's orientation,
+                // even if SystemUI hasn't updated the attrs yet.
+                if (policy.isKeyguardShowingAndNotOccluded() || isUnoccluding) {
+                    return true;
+                }
+            }
+            final int req = w.mAttrs.screenOrientation;
+            if (req == SCREEN_ORIENTATION_UNSPECIFIED || req == SCREEN_ORIENTATION_BEHIND
+                    || req == SCREEN_ORIENTATION_UNSET) {
+                return false;
+            }
+            return true;
+        };
+
+        Tokens(WindowManagerService wms, Type type, String name) {
+            super(wms, type, name);
+        }
+
+        void addChild(WindowToken token) {
+            addChild(token, mWindowComparator);
+        }
+
+        @Override
+        int getOrientation(int candidate) {
+            // Find a window requesting orientation.
+            final WindowState win = getWindow(mGetOrientingWindow);
+
+            if (win == null) {
+                return candidate;
+            }
+            int req = win.mAttrs.screenOrientation;
+            ProtoLog.v(WM_DEBUG_ORIENTATION, "%s forcing orientation to %d for display id=%d",
+                    win, req, mDisplayContent.getDisplayId());
+            if (mWmService.mPolicy.isKeyguardHostWindow(win.mAttrs)) {
+                // SystemUI controls the Keyguard orientation asynchronously, and mAttrs may be
+                // stale. We record / use the last known override.
+                if (req != SCREEN_ORIENTATION_UNSET && req != SCREEN_ORIENTATION_UNSPECIFIED) {
+                    mLastKeyguardForcedOrientation = req;
+                } else {
+                    req = mLastKeyguardForcedOrientation;
+                }
+            }
+            return req;
+        }
+    }
+
+    /**
+     * Top-most DisplayArea under DisplayContent.
+     */
+    public static class Root extends DisplayArea<DisplayArea> {
+        private final Dimmer mDimmer = new Dimmer(this);
+        private final Rect mTmpDimBoundsRect = new Rect();
+
+        Root(WindowManagerService wms) {
+            super(wms, Type.ANY, "DisplayArea.Root");
+        }
+
+        @Override
+        Dimmer getDimmer() {
+            return mDimmer;
+        }
+
+        @Override
+        void prepareSurfaces() {
+            mDimmer.resetDimStates();
+            super.prepareSurfaces();
+            getBounds(mTmpDimBoundsRect);
+
+            if (mDimmer.updateDims(getPendingTransaction(), mTmpDimBoundsRect)) {
+                scheduleAnimation();
+            }
+        }
+    }
+
+    enum Type {
+        /** Can only contain WindowTokens above the APPLICATION_LAYER. */
+        ABOVE_TASKS,
+        /** Can only contain WindowTokens below the APPLICATION_LAYER. */
+        BELOW_TASKS,
+        /** Can contain anything. */
+        ANY;
+
+        static void checkSiblings(Type bottom, Type top) {
+            checkState(!(bottom == ANY && top == ANY), "ANY cannot be a sibling of ANY");
+            checkState(!(bottom != BELOW_TASKS && top == BELOW_TASKS),
+                    bottom + " must be above BELOW_TASKS");
+            checkState(!(bottom == ABOVE_TASKS && top != ABOVE_TASKS),
+                    top + " must be below ABOVE_TASKS");
+        }
+
+        static void checkChild(Type parent, Type child) {
+            switch (parent) {
+                case ABOVE_TASKS:
+                    checkState(child == ABOVE_TASKS, "ABOVE_TASKS can only contain ABOVE_TASKS");
+                    break;
+                case BELOW_TASKS:
+                    checkState(child == BELOW_TASKS, "BELOW_TASKS can only contain BELOW_TASKS");
+                    break;
+            }
+        }
+
+        static Type typeOf(WindowContainer c) {
+            if (c instanceof DisplayArea) {
+                return ((DisplayArea) c).mType;
+            } else if (c instanceof WindowToken && !(c instanceof ActivityRecord)) {
+                return typeOf((WindowToken) c);
+            } else if (c instanceof ActivityStack) {
+                return ANY;
+            } else {
+                throw new IllegalArgumentException("Unknown container: " + c);
+            }
+        }
+
+        private static Type typeOf(WindowToken c) {
+            return c.getWindowLayerFromType() < APPLICATION_LAYER ? BELOW_TASKS : ABOVE_TASKS;
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/wm/DisplayAreaPolicy.java b/services/core/java/com/android/server/wm/DisplayAreaPolicy.java
new file mode 100644
index 0000000..06e7b48
--- /dev/null
+++ b/services/core/java/com/android/server/wm/DisplayAreaPolicy.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2020 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 static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
+
+import com.android.server.wm.DisplayContent.TaskContainers;
+
+/**
+ * Policy that manages DisplayAreas.
+ */
+public abstract class DisplayAreaPolicy {
+    protected final WindowManagerService mWmService;
+    protected final DisplayContent mContent;
+
+    /**
+     * The root DisplayArea. Attach all DisplayAreas to this area (directly or indirectly).
+     */
+    protected final DisplayArea.Root mRoot;
+
+    /**
+     * The IME container. The IME's windows are automatically added to this container.
+     */
+    protected final DisplayArea<? extends WindowContainer> mImeContainer;
+
+    /**
+     * The Tasks container. Tasks etc. are automatically added to this container.
+     */
+    protected final TaskContainers mTaskContainers;
+
+    DisplayAreaPolicy(WindowManagerService wmService,
+            DisplayContent content, DisplayArea.Root root,
+            DisplayArea<? extends WindowContainer> imeContainer, TaskContainers taskContainers) {
+        mWmService = wmService;
+        mContent = content;
+        mRoot = root;
+        mImeContainer = imeContainer;
+        mTaskContainers = taskContainers;
+    }
+
+    /**
+     * Called to ask the policy to set up the DisplayArea hierarchy. At a minimum this must:
+     *
+     * - attach mImeContainer to mRoot (or one of its descendants)
+     * - attach mTaskStacks to mRoot (or one of its descendants)
+     *
+     * Additionally, this is the right place to set up any other DisplayAreas as desired.
+     */
+    public abstract void attachDisplayAreas();
+
+    /**
+     * Called to ask the policy to attach the given WindowToken to the DisplayArea hierarchy.
+     *
+     * This must attach the token to mRoot (or one of its descendants).
+     */
+    public abstract void addWindow(WindowToken token);
+
+    /**
+     * Default policy that has no special features.
+     */
+    public static class Default extends DisplayAreaPolicy {
+
+        public Default(WindowManagerService wmService, DisplayContent content,
+                DisplayArea.Root root,
+                DisplayArea<? extends WindowContainer> imeContainer,
+                TaskContainers taskContainers) {
+            super(wmService, content, root, imeContainer, taskContainers);
+        }
+
+        private final DisplayArea.Tokens mBelow = new DisplayArea.Tokens(mWmService,
+                DisplayArea.Type.BELOW_TASKS, "BelowTasks");
+        private final DisplayArea<DisplayArea> mAbove = new DisplayArea<>(mWmService,
+                DisplayArea.Type.ABOVE_TASKS, "AboveTasks");
+        private final DisplayArea.Tokens mAboveBelowIme = new DisplayArea.Tokens(mWmService,
+                DisplayArea.Type.ABOVE_TASKS, "AboveTasksBelowIme");
+        private final DisplayArea.Tokens mAboveAboveIme = new DisplayArea.Tokens(mWmService,
+                DisplayArea.Type.ABOVE_TASKS, "AboveTasksAboveIme");
+
+        @Override
+        public void attachDisplayAreas() {
+            mRoot.addChild(mBelow, 0);
+            mRoot.addChild(mTaskContainers, 1);
+            mRoot.addChild(mAbove, 2);
+
+            mAbove.addChild(mAboveBelowIme, 0);
+            mAbove.addChild(mImeContainer, 1);
+            mAbove.addChild(mAboveAboveIme, 2);
+        }
+
+        @Override
+        public void addWindow(WindowToken token) {
+            switch (DisplayArea.Type.typeOf(token)) {
+                case ABOVE_TASKS:
+                    if (token.getWindowLayerFromType()
+                            < mWmService.mPolicy.getWindowLayerFromTypeLw(TYPE_INPUT_METHOD)) {
+                        mAboveBelowIme.addChild(token);
+                    } else {
+                        mAboveAboveIme.addChild(token);
+                    }
+                    break;
+                case BELOW_TASKS:
+                    mBelow.addChild(token);
+                    break;
+                default:
+                    throw new IllegalArgumentException("don't know how to sort " + token);
+            }
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 6e479b2..7073707 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -80,7 +80,6 @@
 import static android.view.WindowManager.LayoutParams.TYPE_TOAST;
 import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
 import static android.view.WindowManager.TRANSIT_ACTIVITY_OPEN;
-import static android.view.WindowManager.TRANSIT_KEYGUARD_UNOCCLUDE;
 import static android.view.WindowManager.TRANSIT_TASK_OPEN;
 import static android.view.WindowManager.TRANSIT_TASK_TO_FRONT;
 
@@ -98,9 +97,7 @@
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_STATES;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_TASKS;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_STACK;
-import static com.android.server.wm.DisplayContentProto.ABOVE_APP_WINDOWS;
 import static com.android.server.wm.DisplayContentProto.APP_TRANSITION;
-import static com.android.server.wm.DisplayContentProto.BELOW_APP_WINDOWS;
 import static com.android.server.wm.DisplayContentProto.CHANGING_APPS;
 import static com.android.server.wm.DisplayContentProto.CLOSING_APPS;
 import static com.android.server.wm.DisplayContentProto.DISPLAY_FRAMES;
@@ -109,9 +106,9 @@
 import static com.android.server.wm.DisplayContentProto.DPI;
 import static com.android.server.wm.DisplayContentProto.FOCUSED_APP;
 import static com.android.server.wm.DisplayContentProto.ID;
-import static com.android.server.wm.DisplayContentProto.IME_WINDOWS;
 import static com.android.server.wm.DisplayContentProto.OPENING_APPS;
 import static com.android.server.wm.DisplayContentProto.OVERLAY_WINDOWS;
+import static com.android.server.wm.DisplayContentProto.ROOT_DISPLAY_AREA;
 import static com.android.server.wm.DisplayContentProto.ROTATION;
 import static com.android.server.wm.DisplayContentProto.SCREEN_ROTATION_ANIMATION;
 import static com.android.server.wm.DisplayContentProto.STACKS;
@@ -295,14 +292,7 @@
     /** The containers below are the only child containers {@link #mWindowContainers} can have. */
     // Contains all window containers that are related to apps (Activities)
     private final TaskContainers mTaskContainers = new TaskContainers(mWmService);
-    // Contains all non-app window containers that should be displayed above the app containers
-    // (e.g. Status bar)
-    private final AboveAppWindowContainers mAboveAppWindowsContainers =
-            new AboveAppWindowContainers("mAboveAppWindowsContainers", mWmService);
-    // Contains all non-app window containers that should be displayed below the app containers
-    // (e.g. Wallpaper).
-    private final NonAppWindowContainers mBelowAppWindowsContainers =
-            new NonAppWindowContainers("mBelowAppWindowsContainers", mWmService);
+
     // Contains all IME window containers. Note that the z-ordering of the IME windows will depend
     // on the IME target. We mainly have this container grouping so we can keep track of all the IME
     // window containers together and move them in-sync if/when needed. We use a subclass of
@@ -310,6 +300,11 @@
     // TODO(display-area): is "no magnification" in the comment still true?
     private final ImeContainer mImeWindowsContainers = new ImeContainer(mWmService);
 
+    private final DisplayArea.Root mRootDisplayArea = new DisplayArea.Root(mWmService);
+
+    private final DisplayAreaPolicy mDisplayAreaPolicy = new DisplayAreaPolicy.Default(
+            mWmService, this, mRootDisplayArea, mImeWindowsContainers, mTaskContainers);
+
     private WindowState mTmpWindow;
     private WindowState mTmpWindow2;
     private boolean mUpdateImeTarget;
@@ -401,14 +396,6 @@
     private int mCurrentOverrideConfigurationChanges;
 
     /**
-     * Last orientation forced by the keyguard. It is applied when keyguard is shown and is not
-     * occluded.
-     *
-     * @see NonAppWindowContainers#getOrientation()
-     */
-    private int mLastKeyguardForcedOrientation = SCREEN_ORIENTATION_UNSPECIFIED;
-
-    /**
      * The maximum aspect ratio (longerSide/shorterSide) that is treated as close-to-square. The
      * orientation requests from apps would be ignored if the display is close-to-square.
      */
@@ -1104,18 +1091,16 @@
             // Add non-app token to container hierarchy on the display. App tokens are added through
             // the parent container managing them (e.g. Tasks).
             switch (token.windowType) {
-                case TYPE_WALLPAPER:
-                    mBelowAppWindowsContainers.addChild(token);
-                    break;
                 case TYPE_INPUT_METHOD:
                 case TYPE_INPUT_METHOD_DIALOG:
                     mImeWindowsContainers.addChild(token);
                     break;
                 case TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY:
+                    // TODO(display-area): Migrate to DisplayArea
                     mOverlayContainers.addChild(token);
                     break;
                 default:
-                    mAboveAppWindowsContainers.addChild(token);
+                    mDisplayAreaPolicy.addWindow(token);
                     break;
             }
         }
@@ -2129,13 +2114,7 @@
                 return getLastOrientation();
             }
         }
-        final int orientation = mAboveAppWindowsContainers.getOrientation();
-        if (orientation != SCREEN_ORIENTATION_UNSET) {
-            return orientation;
-        }
-
-        // Top system windows are not requesting an orientation. Start searching from apps.
-        return mTaskContainers.getOrientation();
+        return mRootDisplayArea.getOrientation();
     }
 
     void updateDisplayInfo() {
@@ -2782,23 +2761,12 @@
         final long token = proto.start(fieldId);
         super.dumpDebug(proto, WINDOW_CONTAINER, logLevel);
         proto.write(ID, mDisplayId);
+        mRootDisplayArea.dumpDebug(proto, ROOT_DISPLAY_AREA, logLevel);
         for (int stackNdx = mTaskContainers.getChildCount() - 1; stackNdx >= 0; --stackNdx) {
             final ActivityStack stack = mTaskContainers.getChildAt(stackNdx);
             stack.dumpDebugInnerStackOnly(proto, STACKS, logLevel);
         }
         mDividerControllerLocked.dumpDebug(proto, DOCKED_STACK_DIVIDER_CONTROLLER);
-        for (int i = mAboveAppWindowsContainers.getChildCount() - 1; i >= 0; --i) {
-            final WindowToken windowToken = mAboveAppWindowsContainers.getChildAt(i);
-            windowToken.dumpDebug(proto, ABOVE_APP_WINDOWS, logLevel);
-        }
-        for (int i = mBelowAppWindowsContainers.getChildCount() - 1; i >= 0; --i) {
-            final WindowToken windowToken = mBelowAppWindowsContainers.getChildAt(i);
-            windowToken.dumpDebug(proto, BELOW_APP_WINDOWS, logLevel);
-        }
-        for (int i = mImeWindowsContainers.getChildCount() - 1; i >= 0; --i) {
-            final WindowToken windowToken = mImeWindowsContainers.getChildAt(i);
-            windowToken.dumpDebug(proto, IME_WINDOWS, logLevel);
-        }
         for (int i = mOverlayContainers.getChildCount() - 1; i >= 0; --i) {
             final WindowToken windowToken = mOverlayContainers.getChildAt(i);
             windowToken.dumpDebug(proto, OVERLAY_WINDOWS, logLevel);
@@ -4212,7 +4180,7 @@
      * Window container class that contains all containers on this display relating to Apps.
      * I.e Activities.
      */
-    private final class TaskContainers extends DisplayChildWindowContainer<ActivityStack> {
+    final class TaskContainers extends DisplayArea<ActivityStack> {
         /**
          * A control placed at the appropriate level for transitions to occur.
          */
@@ -4222,16 +4190,15 @@
 
         /**
          * Given that the split-screen divider does not have an AppWindowToken, it
-         * will have to live inside of a "NonAppWindowContainer", in particular
-         * {@link DisplayContent#mAboveAppWindowsContainers}. However, in visual Z order
+         * will have to live inside of a "NonAppWindowContainer". However, in visual Z order
          * it will need to be interleaved with some of our children, appearing on top of
          * both docked stacks but underneath any assistant stacks.
          *
          * To solve this problem we have this anchor control, which will always exist so
          * we can always assign it the correct value in our {@link #assignChildLayers}.
-         * Likewise since it always exists, {@link AboveAppWindowContainers} can always
+         * Likewise since it always exists, we can always
          * assign the divider a layer relative to it. This way we prevent linking lifecycle
-         * events between the two containers.
+         * events between tasks and the divider window.
          */
         SurfaceControl mSplitScreenDividerAnchor = null;
 
@@ -4242,7 +4209,7 @@
         private ActivityStack mRootSplitScreenPrimaryTask = null;
 
         TaskContainers(WindowManagerService service) {
-            super(service);
+            super(service, Type.ANY, "TaskContainers");
         }
 
         /**
@@ -4737,32 +4704,6 @@
         }
     }
 
-    private final class AboveAppWindowContainers extends NonAppWindowContainers {
-        AboveAppWindowContainers(String name, WindowManagerService service) {
-            super(name, service);
-        }
-
-        @Override
-        void assignChildLayers(SurfaceControl.Transaction t) {
-            boolean needAssignIme = mImeWindowsContainers.getSurfaceControl() != null;
-            for (int j = 0; j < mChildren.size(); ++j) {
-                final WindowToken wt = mChildren.get(j);
-
-                wt.assignLayer(t, j);
-                wt.assignChildLayers(t);
-
-                int layer = mWmService.mPolicy.getWindowLayerFromTypeLw(
-                        wt.windowType, wt.mOwnerCanManageAppTokens);
-
-                if (needAssignIme && layer >= mWmService.mPolicy.getWindowLayerFromTypeLw(
-                        TYPE_INPUT_METHOD_DIALOG, true)) {
-                    mImeWindowsContainers.assignRelativeLayer(t, wt.getSurfaceControl(), -1);
-                    needAssignIme = false;
-                }
-            }
-        }
-    }
-
     private class WindowContainers extends DisplayChildWindowContainer<WindowContainer> {
         private final String mName;
 
@@ -4774,13 +4715,10 @@
         @Override
         void assignChildLayers(SurfaceControl.Transaction t) {
             mImeWindowsContainers.setNeedsLayer();
-            mBelowAppWindowsContainers.assignLayer(t, 0);
-            mTaskContainers.assignLayer(t, 1);
-            mAboveAppWindowsContainers.assignLayer(t, 2);
+
+            mRootDisplayArea.assignLayer(t, 0);
 
             final WindowState imeTarget = mInputMethodTarget;
-            boolean needAssignIme = true;
-
             // In the case where we have an IME target that is not in split-screen mode IME
             // assignment is easy. We just need the IME to go directly above the target. This way
             // children of the target will naturally go above the IME and everyone is happy.
@@ -4813,11 +4751,7 @@
 
             // Above we have assigned layers to our children, now we ask them to assign
             // layers to their children.
-            mBelowAppWindowsContainers.assignChildLayers(t);
-            mTaskContainers.assignChildLayers(t);
-            mAboveAppWindowsContainers.assignChildLayers(t);
-            mImeWindowsContainers.assignRelativeLayer(t, getSurfaceControl(), Integer.MAX_VALUE);
-            mImeWindowsContainers.assignChildLayers(t);
+            mRootDisplayArea.assignChildLayers(t);
         }
 
         @Override
@@ -4826,10 +4760,8 @@
         }
 
         void addChildren() {
-            addChild(mBelowAppWindowsContainers, null);
-            addChild(mTaskContainers, null);
-            addChild(mAboveAppWindowsContainers, null);
-            addChild(mImeWindowsContainers, null);
+            addChild(mRootDisplayArea, 0);
+            mDisplayAreaPolicy.attachDisplayAreas();
         }
 
         @Override
@@ -4856,32 +4788,6 @@
                         < mWmService.mPolicy.getWindowLayerFromTypeLw(token2.windowType,
                         token2.mOwnerCanManageAppTokens) ? -1 : 1;
 
-        private final Predicate<WindowState> mGetOrientingWindow = w -> {
-            final WindowManagerPolicy policy = mWmService.mPolicy;
-            if (policy.isKeyguardHostWindow(w.mAttrs)) {
-                if (mWmService.mKeyguardGoingAway) {
-                    return false;
-                }
-                // Consider unoccluding only when all unknown visibilities have been
-                // resolved, as otherwise we just may be starting another occluding activity.
-                final boolean isUnoccluding =
-                        mDisplayContent.mAppTransition.getAppTransition()
-                                == TRANSIT_KEYGUARD_UNOCCLUDE
-                                && mDisplayContent.mUnknownAppVisibilityController.allResolved();
-                // If keyguard is showing, or we're unoccluding, force the keyguard's orientation,
-                // even if SystemUI hasn't updated the attrs yet.
-                if (policy.isKeyguardShowingAndNotOccluded() || isUnoccluding) {
-                    return true;
-                }
-            }
-            final int req = w.mAttrs.screenOrientation;
-            if (req == SCREEN_ORIENTATION_UNSPECIFIED || req == SCREEN_ORIENTATION_BEHIND
-                    || req == SCREEN_ORIENTATION_UNSET) {
-                return false;
-            }
-            return true;
-        };
-
         private final String mName;
         private final Dimmer mDimmer = new Dimmer(this);
         private final Rect mTmpDimBoundsRect = new Rect();
@@ -4903,26 +4809,9 @@
 
         @Override
         int getOrientation(int candidate) {
-            // Find a window requesting orientation.
-            final WindowState win = getWindow(mGetOrientingWindow);
-
-            if (win != null) {
-                int req = win.mAttrs.screenOrientation;
-                ProtoLog.v(WM_DEBUG_ORIENTATION,
-                        "%s forcing orientation to %d for display id=%d", win, req,
-                        mDisplayId);
-                if (mWmService.mPolicy.isKeyguardHostWindow(win.mAttrs)) {
-                    // SystemUI controls the Keyguard orientation asynchronously, and mAttrs may be
-                    // stale. We record / use the last known override.
-                    if (req != SCREEN_ORIENTATION_UNSET && req != SCREEN_ORIENTATION_UNSPECIFIED) {
-                        mDisplayContent.mLastKeyguardForcedOrientation = req;
-                    } else {
-                        req = mDisplayContent.mLastKeyguardForcedOrientation;
-                    }
-                }
-                return req;
-            }
-            return candidate;
+            ProtoLog.w(WM_DEBUG_ORIENTATION, "NonAppWindowContainer cannot set orientation: %s",
+                    this);
+            return SCREEN_ORIENTATION_UNSET;
         }
 
         @Override
@@ -4957,11 +4846,11 @@
      * - the container doesn't always participate in window traversal, according to
      *   {@link #skipImeWindowsDuringTraversal()}
      */
-    private class ImeContainer extends NonAppWindowContainers {
+    private static class ImeContainer extends DisplayArea.Tokens {
         boolean mNeedsLayer = false;
 
         ImeContainer(WindowManagerService wms) {
-            super("ImeContainer", wms);
+            super(wms, Type.ABOVE_TASKS, "ImeContainer");
         }
 
         public void setNeedsLayer() {
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayAreaTest.java b/services/tests/wmtests/src/com/android/server/wm/DisplayAreaTest.java
new file mode 100644
index 0000000..618e608
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayAreaTest.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2020 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 static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
+import static android.view.WindowManager.LayoutParams.TYPE_PRESENTATION;
+import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
+
+import static com.android.server.wm.DisplayArea.Type.ABOVE_TASKS;
+import static com.android.server.wm.DisplayArea.Type.ANY;
+import static com.android.server.wm.DisplayArea.Type.BELOW_TASKS;
+import static com.android.server.wm.DisplayArea.Type.checkChild;
+import static com.android.server.wm.DisplayArea.Type.checkSiblings;
+import static com.android.server.wm.DisplayArea.Type.typeOf;
+import static com.android.server.wm.testing.Assert.assertThrows;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.mock;
+
+import android.os.Binder;
+import android.platform.test.annotations.Presubmit;
+import android.view.SurfaceControl;
+
+import org.junit.Rule;
+import org.junit.Test;
+
+@Presubmit
+public class DisplayAreaTest {
+
+    @Rule
+    public SystemServicesTestRule mWmsRule = new SystemServicesTestRule();
+
+    @Test
+    public void testDisplayArea_positionChanged_throwsIfIncompatibleChild() {
+        WindowManagerService wms = mWmsRule.getWindowManagerService();
+        DisplayArea<WindowContainer> parent = new DisplayArea<>(wms, BELOW_TASKS, "Parent");
+        DisplayArea<WindowContainer> child = new DisplayArea<>(wms, ANY, "Child");
+
+        assertThrows(IllegalStateException.class, () -> parent.addChild(child, 0));
+    }
+
+    @Test
+    public void testDisplayArea_positionChanged_throwsIfIncompatibleSibling() {
+        WindowManagerService wms = mWmsRule.getWindowManagerService();
+        DisplayArea<WindowContainer> parent = new SurfacelessDisplayArea<>(wms, ANY, "Parent");
+        DisplayArea<WindowContainer> child1 = new DisplayArea<>(wms, ANY, "Child1");
+        DisplayArea<WindowContainer> child2 = new DisplayArea<>(wms, ANY, "Child2");
+
+        parent.addChild(child1, 0);
+        assertThrows(IllegalStateException.class, () -> parent.addChild(child2, 0));
+    }
+
+    @Test
+    public void testType_typeOf() {
+        WindowManagerService wms = mWmsRule.getWindowManagerService();
+
+        assertEquals(ABOVE_TASKS, typeOf(new DisplayArea<>(wms, ABOVE_TASKS, "test")));
+        assertEquals(ANY, typeOf(new DisplayArea<>(wms, ANY, "test")));
+        assertEquals(BELOW_TASKS, typeOf(new DisplayArea<>(wms, BELOW_TASKS, "test")));
+
+        assertEquals(ABOVE_TASKS, typeOf(createWindowToken(TYPE_APPLICATION_OVERLAY)));
+        assertEquals(ABOVE_TASKS, typeOf(createWindowToken(TYPE_PRESENTATION)));
+        assertEquals(BELOW_TASKS, typeOf(createWindowToken(TYPE_WALLPAPER)));
+
+        assertThrows(IllegalArgumentException.class, () -> typeOf(mock(ActivityRecord.class)));
+        assertThrows(IllegalArgumentException.class, () -> typeOf(mock(WindowContainer.class)));
+    }
+
+    @Test
+    public void testType_checkSiblings() {
+        checkSiblings(BELOW_TASKS, BELOW_TASKS);
+        checkSiblings(BELOW_TASKS, ANY);
+        checkSiblings(BELOW_TASKS, ABOVE_TASKS);
+        checkSiblings(ANY, ABOVE_TASKS);
+        checkSiblings(ABOVE_TASKS, ABOVE_TASKS);
+
+        assertThrows(IllegalStateException.class, () -> checkSiblings(ABOVE_TASKS, BELOW_TASKS));
+        assertThrows(IllegalStateException.class, () -> checkSiblings(ABOVE_TASKS, ANY));
+        assertThrows(IllegalStateException.class, () -> checkSiblings(ANY, ANY));
+        assertThrows(IllegalStateException.class, () -> checkSiblings(ANY, BELOW_TASKS));
+    }
+
+    @Test
+    public void testType_checkChild() {
+        checkChild(ANY, ANY);
+        checkChild(ANY, ABOVE_TASKS);
+        checkChild(ANY, BELOW_TASKS);
+        checkChild(ABOVE_TASKS, ABOVE_TASKS);
+        checkChild(BELOW_TASKS, BELOW_TASKS);
+
+        assertThrows(IllegalStateException.class, () -> checkChild(ABOVE_TASKS, BELOW_TASKS));
+        assertThrows(IllegalStateException.class, () -> checkChild(ABOVE_TASKS, ANY));
+        assertThrows(IllegalStateException.class, () -> checkChild(BELOW_TASKS, ABOVE_TASKS));
+        assertThrows(IllegalStateException.class, () -> checkChild(BELOW_TASKS, ANY));
+    }
+
+    private WindowToken createWindowToken(int type) {
+        return new WindowToken(mWmsRule.getWindowManagerService(), new Binder(),
+                type, false /* persist */, null /* displayContent */,
+                false /* canManageTokens */);
+    }
+
+    private static class SurfacelessDisplayArea<T extends WindowContainer> extends DisplayArea<T> {
+
+        SurfacelessDisplayArea(WindowManagerService wms, Type type, String name) {
+            super(wms, type, name);
+        }
+
+        @Override
+        SurfaceControl.Builder makeChildSurface(WindowContainer child) {
+            return new MockSurfaceControlBuilder();
+        }
+    }
+}
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
index 1637370..2ea00ce 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -139,11 +139,11 @@
                 mAppWindow,
                 mChildAppWindowAbove,
                 mDockedDividerWindow,
+                mImeWindow,
+                mImeDialogWindow,
                 mStatusBarWindow,
                 mNotificationShadeWindow,
-                mNavBarWindow,
-                mImeWindow,
-                mImeDialogWindow));
+                mNavBarWindow));
     }
 
     @Test
@@ -232,11 +232,11 @@
                 mChildAppWindowAbove,
                 mDockedDividerWindow,
                 voiceInteractionWindow,
+                mImeWindow,
+                mImeDialogWindow,
                 mStatusBarWindow,
                 mNotificationShadeWindow,
-                mNavBarWindow,
-                mImeWindow,
-                mImeDialogWindow));
+                mNavBarWindow));
     }
 
     @Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/testing/Assert.java b/services/tests/wmtests/src/com/android/server/wm/testing/Assert.java
new file mode 100644
index 0000000..1e98277
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/wm/testing/Assert.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2020 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.testing;
+
+/**
+ * Assertions for WM tests.
+ */
+public class Assert {
+
+    /**
+     * Runs {@code r} and asserts that an exception of type {@code expectedThrowable} is thrown.
+     * @param expectedThrowable the type of throwable that is expected to be thrown
+     * @param r the {@link Runnable} which is run and expected to throw.
+     * @throws AssertionError if {@code r} does not throw, or throws a runnable that is not an
+     *                        instance of {@code expectedThrowable}.
+     */
+    // TODO: remove once Android migrates to JUnit 4.13, which provides assertThrows
+    public static void assertThrows(Class<? extends Throwable> expectedThrowable, Runnable r) {
+        try {
+            r.run();
+        } catch (Throwable t) {
+            if (expectedThrowable.isInstance(t)) {
+                return;
+            } else if (t instanceof Exception) {
+                throw new AssertionError("Expected " + expectedThrowable
+                        + ", but got " + t.getClass(), t);
+            } else {
+                // Re-throw Errors and other non-Exception throwables.
+                throw t;
+            }
+        }
+        throw new AssertionError("Expected " + expectedThrowable + ", but nothing was thrown");
+    }
+}
diff --git a/services/tests/wmtests/src/com/android/server/wm/testing/AssertTest.java b/services/tests/wmtests/src/com/android/server/wm/testing/AssertTest.java
new file mode 100644
index 0000000..df12761
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/wm/testing/AssertTest.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2020 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.testing;
+
+import static com.android.server.wm.testing.Assert.assertThrows;
+
+import static org.junit.Assert.assertTrue;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+
+public class AssertTest {
+
+    @Rule
+    public ExpectedException mExpectedException = ExpectedException.none();
+
+    @Test
+    public void assertThrows_runsRunnable() {
+        boolean[] ran = new boolean[] { false };
+        assertThrows(TestException.class, () -> {
+            ran[0] = true;
+            throw new TestException();
+        });
+        assertTrue(ran[0]);
+    }
+
+    @Test
+    public void assertThrows_failsIfNothingThrown() {
+        mExpectedException.expect(AssertionError.class);
+        assertThrows(TestException.class, () -> {
+        });
+    }
+
+    @Test
+    public void assertThrows_failsIfWrongExceptionThrown() {
+        mExpectedException.expect(AssertionError.class);
+        assertThrows(TestException.class, () -> {
+            throw new RuntimeException();
+        });
+    }
+
+    @Test
+    public void assertThrows_succeedsIfGivenExceptionThrown() {
+        assertThrows(TestException.class, () -> {
+            throw new TestException();
+        });
+    }
+
+    @Test
+    public void assertThrows_succeedsIfSubExceptionThrown() {
+        assertThrows(RuntimeException.class, () -> {
+            throw new TestException();
+        });
+    }
+
+    @Test
+    public void assertThrows_rethrowsUnexpectedErrors() {
+        mExpectedException.expect(TestError.class);
+        assertThrows(TestException.class, () -> {
+            throw new TestError();
+        });
+    }
+
+    static class TestException extends RuntimeException {
+    }
+
+    static class TestError extends Error {
+    }
+
+}