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/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 {
+    }
+
+}