Add API to create new TaskDisplayArea at runtime

Allow DA organizer to create new TaskDisplayArea.

Fix: 173455508
Test: atest WmTests:DisplayAreaOrganizerTest
Change-Id: I7d4e88f43ae14561720c6942ca0e9f82dc28e9f6
diff --git a/core/java/android/window/DisplayAreaOrganizer.java b/core/java/android/window/DisplayAreaOrganizer.java
index 6e20452..1ac188c 100644
--- a/core/java/android/window/DisplayAreaOrganizer.java
+++ b/core/java/android/window/DisplayAreaOrganizer.java
@@ -101,6 +101,19 @@
     public static final int FEATURE_VENDOR_FIRST = FEATURE_SYSTEM_LAST + 1;
 
     /**
+     * Last possible vendor specific display area id.
+     * @hide
+     */
+    public static final int FEATURE_VENDOR_LAST = FEATURE_VENDOR_FIRST + 10_000;
+
+    /**
+     * Task display areas that can be created at runtime start with this value.
+     * @see #createTaskDisplayArea(int, int, String)
+     * @hide
+     */
+    public static final int FEATURE_RUNTIME_TASK_CONTAINER_FIRST = FEATURE_VENDOR_LAST + 1;
+
+    /**
      * Registers a DisplayAreaOrganizer to manage display areas for a given feature. A feature can
      * not be registered by multiple organizers at the same time.
      *
@@ -132,6 +145,50 @@
     }
 
     /**
+     * Creates a persistent task display area. It will be added to be the top most task display area
+     * in the root.
+     *
+     * The new created TDA is organized by the organizer, and will be deleted on calling
+     * {@link #deleteTaskDisplayArea(WindowContainerToken)} or {@link #unregisterOrganizer()}.
+     *
+     * @param displayId the display to create the new task display area in.
+     * @param rootFeatureId the root display area to create the new task display area in. Caller can
+     *                      use {@link #FEATURE_ROOT} as the root of the logical display.
+     * @param name the name for the new task display area.
+     * @return the new created task display area.
+     * @throws IllegalArgumentException if failed to create a new task display area.
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS)
+    @CallSuper
+    @NonNull
+    public DisplayAreaAppearedInfo createTaskDisplayArea(int displayId, int rootFeatureId,
+            @NonNull String name) {
+        try {
+            return getController().createTaskDisplayArea(
+                    mInterface, displayId, rootFeatureId, name);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Deletes a persistent task display area. It can only be one that created by an organizer.
+     *
+     * @throws IllegalArgumentException if failed to delete the task display area.
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS)
+    @CallSuper
+    public void deleteTaskDisplayArea(@NonNull WindowContainerToken taskDisplayArea) {
+        try {
+            getController().deleteTaskDisplayArea(taskDisplayArea);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Called when a DisplayArea of the registered window type can be controlled by this organizer.
      * It will not be called for the DisplayAreas that exist when {@link #registerOrganizer(int)} is
      * called.
diff --git a/core/java/android/window/IDisplayAreaOrganizerController.aidl b/core/java/android/window/IDisplayAreaOrganizerController.aidl
index edabcf8..26fa434 100644
--- a/core/java/android/window/IDisplayAreaOrganizerController.aidl
+++ b/core/java/android/window/IDisplayAreaOrganizerController.aidl
@@ -19,6 +19,7 @@
 import android.content.pm.ParceledListSlice;
 import android.window.DisplayAreaAppearedInfo;
 import android.window.IDisplayAreaOrganizer;
+import android.window.WindowContainerToken;
 
 /** @hide */
 interface IDisplayAreaOrganizerController {
@@ -37,4 +38,28 @@
      * Unregisters a previously registered display area organizer.
      */
     void unregisterOrganizer(in IDisplayAreaOrganizer organizer);
+
+    /**
+     * Creates a persistent task display area. It will be added to be the top most task display area
+     * in the root.
+     *
+     * The new created TDA is organized by the organizer, and will be deleted on calling
+     * {@link #deleteTaskDisplayArea(WindowContainerToken)} or {@link #unregisterOrganizer()}.
+     *
+     * @param displayId the display to create the new task display area in.
+     * @param rootFeatureId the root display area to create the new task display area in. Caller can
+     *                      use {@link #FEATURE_ROOT} as the root of the logical display.
+     * @param name the name for the new task display area.
+     * @return the new created task display area.
+     * @throws IllegalArgumentException if failed to create a new task display area.
+     */
+    DisplayAreaAppearedInfo createTaskDisplayArea(in IDisplayAreaOrganizer organizer, int displayId,
+        int rootFeatureId, in String name);
+
+    /**
+     * Deletes a persistent task display area. It can only be one that created by an organizer.
+     *
+     * @throws IllegalArgumentException if failed to delete the task display area.
+     */
+    void deleteTaskDisplayArea(in WindowContainerToken taskDisplayArea);
 }
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index 52da707..1031023 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -961,6 +961,12 @@
       "group": "WM_DEBUG_WINDOW_ORGANIZER",
       "at": "com\/android\/server\/wm\/TaskOrganizerController.java"
     },
+    "-948446688": {
+      "message": "Create TaskDisplayArea uid=%d",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_WINDOW_ORGANIZER",
+      "at": "com\/android\/server\/wm\/DisplayAreaOrganizerController.java"
+    },
     "-937498525": {
       "message": "Executing finish of failed to pause activity: %s",
       "level": "VERBOSE",
@@ -1273,6 +1279,12 @@
       "group": "WM_DEBUG_STATES",
       "at": "com\/android\/server\/wm\/Task.java"
     },
+    "-597091183": {
+      "message": "Delete TaskDisplayArea uid=%d",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_WINDOW_ORGANIZER",
+      "at": "com\/android\/server\/wm\/DisplayAreaOrganizerController.java"
+    },
     "-593535526": {
       "message": "Binding proc %s with config %s",
       "level": "VERBOSE",
@@ -1495,12 +1507,6 @@
       "group": "WM_DEBUG_APP_TRANSITIONS",
       "at": "com\/android\/server\/wm\/ActivityRecord.java"
     },
-    "-371630969": {
-      "message": "New wallpaper target=%s, oldWallpaper=%s, openingApps=%s, closingApps=%s",
-      "level": "VERBOSE",
-      "group": "WM_DEBUG_APP_TRANSITIONS",
-      "at": "com\/android\/server\/wm\/AppTransitionController.java"
-    },
     "-354571697": {
       "message": "Existence Changed in transition %d: %s",
       "level": "VERBOSE",
@@ -2101,12 +2107,6 @@
       "group": "WM_DEBUG_ORIENTATION",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
-    "355940361": {
-      "message": "Config is destroying non-running %s",
-      "level": "VERBOSE",
-      "group": "WM_DEBUG_CONFIGURATION",
-      "at": "com\/android\/server\/wm\/ActivityRecord.java"
-    },
     "371173718": {
       "message": "finishSync cancel=%b for %s",
       "level": "VERBOSE",
diff --git a/services/core/java/com/android/server/wm/DisplayArea.java b/services/core/java/com/android/server/wm/DisplayArea.java
index 8ad2958..48e0300 100644
--- a/services/core/java/com/android/server/wm/DisplayArea.java
+++ b/services/core/java/com/android/server/wm/DisplayArea.java
@@ -382,6 +382,13 @@
 
     @Nullable
     @Override
+    <R> R getItemFromDisplayAreas(Function<DisplayArea, R> callback) {
+        final R item = super.getItemFromDisplayAreas(callback);
+        return item != null ? item : callback.apply(this);
+    }
+
+    @Nullable
+    @Override
     <R> R getItemFromTaskDisplayAreas(Function<TaskDisplayArea, R> callback,
             boolean traverseTopToBottom) {
         // Only DisplayArea of Type.ANY may contain TaskDisplayArea as children.
diff --git a/services/core/java/com/android/server/wm/DisplayAreaOrganizerController.java b/services/core/java/com/android/server/wm/DisplayAreaOrganizerController.java
index 43b9a21..c475da3 100644
--- a/services/core/java/com/android/server/wm/DisplayAreaOrganizerController.java
+++ b/services/core/java/com/android/server/wm/DisplayAreaOrganizerController.java
@@ -16,7 +16,10 @@
 
 package com.android.server.wm;
 
+import static android.window.DisplayAreaOrganizer.FEATURE_RUNTIME_TASK_CONTAINER_FIRST;
+
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_WINDOW_ORGANIZER;
+import static com.android.server.wm.DisplayArea.Type.ANY;
 
 import android.content.pm.ParceledListSlice;
 import android.os.Binder;
@@ -26,6 +29,7 @@
 import android.window.DisplayAreaAppearedInfo;
 import android.window.IDisplayAreaOrganizer;
 import android.window.IDisplayAreaOrganizerController;
+import android.window.WindowContainerToken;
 
 import com.android.internal.protolog.common.ProtoLog;
 
@@ -36,6 +40,12 @@
 public class DisplayAreaOrganizerController extends IDisplayAreaOrganizerController.Stub {
     private static final String TAG = "DisplayAreaOrganizerController";
 
+    /**
+     * Next available feature id for a runtime task display area.
+     * @see #createTaskDisplayArea(IDisplayAreaOrganizer organizer, int, int, String)
+     */
+    private int mNextTaskDisplayAreaFeatureId = FEATURE_RUNTIME_TASK_CONTAINER_FIRST;
+
     final ActivityTaskManagerService mService;
     private final WindowManagerGlobalLock mGlobalLock;
     private final HashMap<Integer, IDisplayAreaOrganizer> mOrganizersByFeatureIds = new HashMap();
@@ -92,10 +102,8 @@
                 final List<DisplayAreaAppearedInfo> displayAreaInfos = new ArrayList<>();
                 mService.mRootWindowContainer.forAllDisplayAreas((da) -> {
                     if (da.mFeatureId != feature) return;
-                    da.setOrganizer(organizer, true /* skipDisplayAreaAppeared */);
-                    displayAreaInfos.add(new DisplayAreaAppearedInfo(da.getDisplayAreaInfo(),
-                            new SurfaceControl(da.getSurfaceControl(),
-                                    "DisplayAreaOrganizerController.registerOrganizer")));
+                    displayAreaInfos.add(organizeDisplayArea(organizer, da,
+                            "DisplayAreaOrganizerController.registerOrganizer"));
                 });
 
                 mOrganizersByFeatureIds.put(feature, organizer);
@@ -124,6 +132,77 @@
         }
     }
 
+    @Override
+    public DisplayAreaAppearedInfo createTaskDisplayArea(IDisplayAreaOrganizer organizer,
+            int displayId, int rootFeatureId, String name) {
+        enforceTaskPermission("createTaskDisplayArea()");
+        final long uid = Binder.getCallingUid();
+        final long origId = Binder.clearCallingIdentity();
+        try {
+            synchronized (mGlobalLock) {
+                ProtoLog.v(WM_DEBUG_WINDOW_ORGANIZER, "Create TaskDisplayArea uid=%d", uid);
+
+                final DisplayContent display =
+                        mService.mRootWindowContainer.getDisplayContent(displayId);
+                if (display == null) {
+                    throw new IllegalArgumentException("createTaskDisplayArea unknown displayId="
+                            + displayId);
+                }
+
+                final DisplayArea root = display.getItemFromDisplayAreas(da ->
+                        da.asRootDisplayArea() != null && da.mFeatureId == rootFeatureId
+                                ? da
+                                : null);
+                if (root == null) {
+                    throw new IllegalArgumentException("Can't find RootDisplayArea with featureId="
+                            + rootFeatureId);
+                }
+
+                final int taskDisplayAreaFeatureId = mNextTaskDisplayAreaFeatureId++;
+                final DeathRecipient dr = new DeathRecipient(organizer, taskDisplayAreaFeatureId);
+                try {
+                    organizer.asBinder().linkToDeath(dr, 0);
+                } catch (RemoteException e) {
+                    // Oh well...
+                }
+
+                final TaskDisplayArea tda = createTaskDisplayArea(root.asRootDisplayArea(), name,
+                        taskDisplayAreaFeatureId);
+                return organizeDisplayArea(organizer, tda,
+                        "DisplayAreaOrganizerController.createTaskDisplayArea");
+            }
+        } finally {
+            Binder.restoreCallingIdentity(origId);
+        }
+    }
+
+    @Override
+    public void deleteTaskDisplayArea(WindowContainerToken token) {
+        enforceTaskPermission("deleteTaskDisplayArea()");
+        final long uid = Binder.getCallingUid();
+        final long origId = Binder.clearCallingIdentity();
+        try {
+            synchronized (mGlobalLock) {
+                ProtoLog.v(WM_DEBUG_WINDOW_ORGANIZER, "Delete TaskDisplayArea uid=%d", uid);
+
+                final WindowContainer wc = WindowContainer.fromBinder(token.asBinder());
+                if (wc == null || wc.asTaskDisplayArea() == null) {
+                    throw new IllegalArgumentException("Can't resolve TaskDisplayArea from token");
+                }
+                final TaskDisplayArea taskDisplayArea = wc.asTaskDisplayArea();
+                if (!taskDisplayArea.mCreatedByOrganizer) {
+                    throw new IllegalArgumentException(
+                            "Attempt to delete TaskDisplayArea not created by organizer "
+                                    + "TaskDisplayArea=" + taskDisplayArea);
+                }
+
+                deleteTaskDisplayArea(taskDisplayArea);
+            }
+        } finally {
+            Binder.restoreCallingIdentity(origId);
+        }
+    }
+
     void onDisplayAreaAppeared(IDisplayAreaOrganizer organizer, DisplayArea da) {
         ProtoLog.v(WM_DEBUG_WINDOW_ORGANIZER, "DisplayArea appeared name=%s", da.getName());
         try {
@@ -157,8 +236,71 @@
         IBinder organizerBinder = organizer.asBinder();
         mService.mRootWindowContainer.forAllDisplayAreas((da) -> {
             if (da.mOrganizer != null && da.mOrganizer.asBinder().equals(organizerBinder)) {
-                da.setOrganizer(null);
+                if (da.isTaskDisplayArea() && da.asTaskDisplayArea().mCreatedByOrganizer) {
+                    // Delete the organizer created TDA when unregister.
+                    deleteTaskDisplayArea(da.asTaskDisplayArea());
+                } else {
+                    da.setOrganizer(null);
+                }
             }
         });
     }
+
+    private DisplayAreaAppearedInfo organizeDisplayArea(IDisplayAreaOrganizer organizer,
+            DisplayArea displayArea, String callsite) {
+        displayArea.setOrganizer(organizer, true /* skipDisplayAreaAppeared */);
+        return new DisplayAreaAppearedInfo(displayArea.getDisplayAreaInfo(),
+                new SurfaceControl(displayArea.getSurfaceControl(), callsite));
+    }
+
+    private TaskDisplayArea createTaskDisplayArea(RootDisplayArea root, String name,
+            int taskDisplayAreaFeatureId) {
+        final TaskDisplayArea taskDisplayArea = new TaskDisplayArea(root.mDisplayContent,
+                root.mWmService, name, taskDisplayAreaFeatureId, true /* createdByOrganizer */);
+
+        // Find the top most DA that can contain Task (either a TDA or a DisplayAreaGroup).
+        final DisplayArea topTaskContainer = root.getItemFromDisplayAreas(da -> {
+            if (da.mType != ANY) {
+                return null;
+            }
+
+            final RootDisplayArea rootDA = da.getRootDisplayArea();
+            if (rootDA == root || rootDA == da) {
+                // Either it is the top TDA below the root or it is a DisplayAreaGroup.
+                return da;
+            }
+            return null;
+        });
+        if (topTaskContainer == null) {
+            throw new IllegalStateException("Root must either contain TDA or DAG root=" + root);
+        }
+
+        // Insert the TaskDisplayArea as the top Task container.
+        final WindowContainer parent = topTaskContainer.getParent();
+        final int index = parent.mChildren.indexOf(topTaskContainer) + 1;
+        parent.addChild(taskDisplayArea, index);
+
+        return taskDisplayArea;
+    }
+
+    private void deleteTaskDisplayArea(TaskDisplayArea taskDisplayArea) {
+        taskDisplayArea.setOrganizer(null);
+        mService.mRootWindowContainer.mTaskSupervisor.beginDeferResume();
+
+        // TaskDisplayArea#remove() move the stacks to the default TaskDisplayArea.
+        Task lastReparentedStack;
+        try {
+            lastReparentedStack = taskDisplayArea.remove();
+        } finally {
+            mService.mRootWindowContainer.mTaskSupervisor.endDeferResume();
+        }
+
+        taskDisplayArea.removeImmediately();
+
+        // Only update focus/visibility for the last one because there may be many stacks are
+        // reparented and the intermediate states are unnecessary.
+        if (lastReparentedStack != null) {
+            lastReparentedStack.postReparent();
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/wm/DisplayAreaPolicyBuilder.java b/services/core/java/com/android/server/wm/DisplayAreaPolicyBuilder.java
index 80ec722..6a42087 100644
--- a/services/core/java/com/android/server/wm/DisplayAreaPolicyBuilder.java
+++ b/services/core/java/com/android/server/wm/DisplayAreaPolicyBuilder.java
@@ -172,10 +172,16 @@
             throw new IllegalStateException("Root must be set for the display area policy.");
         }
 
+        final Set<Integer> rootIdSet = new ArraySet<>();
+        rootIdSet.add(mRootHierarchyBuilder.mRoot.mFeatureId);
         boolean containsImeContainer = mRootHierarchyBuilder.mImeContainer != null;
         boolean containsDefaultTda = containsDefaultTaskDisplayArea(mRootHierarchyBuilder);
         for (int i = 0; i < mDisplayAreaGroupHierarchyBuilders.size(); i++) {
             HierarchyBuilder hierarchyBuilder = mDisplayAreaGroupHierarchyBuilders.get(i);
+            if (!rootIdSet.add(hierarchyBuilder.mRoot.mFeatureId)) {
+                throw new IllegalStateException("There should not be two RootDisplayAreas with id "
+                        + hierarchyBuilder.mRoot.mFeatureId);
+            }
             if (hierarchyBuilder.mTaskDisplayAreas.isEmpty()) {
                 throw new IllegalStateException(
                         "DisplayAreaGroup must contain at least one TaskDisplayArea.");
diff --git a/services/core/java/com/android/server/wm/RootDisplayArea.java b/services/core/java/com/android/server/wm/RootDisplayArea.java
index 1e5d045..da04f43 100644
--- a/services/core/java/com/android/server/wm/RootDisplayArea.java
+++ b/services/core/java/com/android/server/wm/RootDisplayArea.java
@@ -57,6 +57,11 @@
         return this;
     }
 
+    @Override
+    RootDisplayArea asRootDisplayArea() {
+        return this;
+    }
+
     /** Whether the orientation (based on dimensions) of this root is different from the Display. */
     boolean isOrientationDifferentFromDisplay() {
         return false;
diff --git a/services/core/java/com/android/server/wm/TaskDisplayArea.java b/services/core/java/com/android/server/wm/TaskDisplayArea.java
index c02e7ad..81b8200 100644
--- a/services/core/java/com/android/server/wm/TaskDisplayArea.java
+++ b/services/core/java/com/android/server/wm/TaskDisplayArea.java
@@ -157,12 +157,24 @@
      */
     private int mLastLeafTaskToFrontId;
 
+    /**
+     * Whether this TaskDisplayArea was created by a {@link android.window.DisplayAreaOrganizer}.
+     * If {@code true}, this will be removed when the organizer is unregistered.
+     */
+    final boolean mCreatedByOrganizer;
+
     TaskDisplayArea(DisplayContent displayContent, WindowManagerService service, String name,
             int displayAreaFeature) {
+        this(displayContent, service, name, displayAreaFeature, false /* createdByOrganizer */);
+    }
+
+    TaskDisplayArea(DisplayContent displayContent, WindowManagerService service, String name,
+            int displayAreaFeature, boolean createdByOrganizer) {
         super(service, Type.ANY, name, displayAreaFeature);
         mDisplayContent = displayContent;
         mRootWindowContainer = service.mRoot;
         mAtmService = service.mAtmService;
+        mCreatedByOrganizer = createdByOrganizer;
     }
 
     /**
@@ -1914,6 +1926,11 @@
     }
 
     @Override
+    TaskDisplayArea asTaskDisplayArea() {
+        return this;
+    }
+
+    @Override
     void dump(PrintWriter pw, String prefix, boolean dumpAll) {
         pw.println(prefix + "TaskDisplayArea " + getName());
         final String doublePrefix = prefix + "  ";
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index 4574be7..06449c6 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -1814,7 +1814,7 @@
     /**
      * For all {@link TaskDisplayArea} at or below this container call the callback.
      * @param callback Applies on each {@link TaskDisplayArea} found and stops the search if it
-     *                  returns {@code true}.
+     *                 returns {@code true}.
      * @param traverseTopToBottom If {@code true}, traverses the hierarchy from top-to-bottom in
      *                            terms of z-order, else from bottom-to-top.
      * @return {@code true} if the search ended before we reached the end of the hierarchy due to
@@ -1837,7 +1837,7 @@
      * For all {@link TaskDisplayArea} at or below this container call the callback. Traverses from
      * top to bottom in terms of z-order.
      * @param callback Applies on each {@link TaskDisplayArea} found and stops the search if it
-     *                  returns {@code true}.
+     *                 returns {@code true}.
      * @return {@code true} if the search ended before we reached the end of the hierarchy due to
      *         callback returning {@code true}.
      */
@@ -1873,7 +1873,7 @@
      * Performs a reduction on all {@link TaskDisplayArea} at or below this container, using the
      * provided initial value and an accumulation function, and returns the reduced value.
      * @param accumulator Applies on each {@link TaskDisplayArea} found with the accumulative result
-     *                 from the previous call.
+     *                    from the previous call.
      * @param initValue The initial value to pass to the accumulating function with the first
      *                  {@link TaskDisplayArea}.
      * @param traverseTopToBottom If {@code true}, traverses the hierarchy from top-to-bottom in
@@ -1899,7 +1899,7 @@
      * provided initial value and an accumulation function, and returns the reduced value. Traverses
      * from top to bottom in terms of z-order.
      * @param accumulator Applies on each {@link TaskDisplayArea} found with the accumulative result
-     *                 from the previous call.
+     *                    from the previous call.
      * @param initValue The initial value to pass to the accumulating function with the first
      *                  {@link TaskDisplayArea}.
      * @return the accumulative result.
@@ -1912,9 +1912,29 @@
 
     /**
      * Finds the first non {@code null} return value from calling the callback on all
+     * {@link DisplayArea} at or below this container. Traverses from top to bottom in terms of
+     * z-order.
+     * @param callback Applies on each {@link DisplayArea} found and stops the search if it
+     *                 returns non {@code null}.
+     * @return the first returned object that is not {@code null}. Returns {@code null} if not
+     *         found.
+     */
+    @Nullable
+    <R> R getItemFromDisplayAreas(Function<DisplayArea, R> callback) {
+        for (int i = mChildren.size() - 1; i >= 0; --i) {
+            R result = (R) mChildren.get(i).getItemFromDisplayAreas(callback);
+            if (result != null) {
+                return result;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Finds the first non {@code null} return value from calling the callback on all
      * {@link TaskDisplayArea} at or below this container.
      * @param callback Applies on each {@link TaskDisplayArea} found and stops the search if it
-     *                  returns non {@code null}.
+     *                 returns non {@code null}.
      * @param traverseTopToBottom If {@code true}, traverses the hierarchy from top-to-bottom in
      *                            terms of z-order, else from bottom-to-top.
      * @return the first returned object that is not {@code null}. Returns {@code null} if not
@@ -1941,7 +1961,7 @@
      * {@link TaskDisplayArea} at or below this container. Traverses from top to bottom in terms of
      * z-order.
      * @param callback Applies on each {@link TaskDisplayArea} found and stops the search if it
-     *                  returns non {@code null}.
+     *                 returns non {@code null}.
      * @return the first returned object that is not {@code null}. Returns {@code null} if not
      *         found.
      */
@@ -2884,6 +2904,16 @@
         return null;
     }
 
+    /** Cheap way of doing cast and instanceof. */
+    RootDisplayArea asRootDisplayArea() {
+        return null;
+    }
+
+    /** Cheap way of doing cast and instanceof. */
+    TaskDisplayArea asTaskDisplayArea() {
+        return null;
+    }
+
     /**
      * @return {@code true} if window container is manage by a
      *          {@link android.window.WindowOrganizer}
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayAreaOrganizerTest.java b/services/tests/wmtests/src/com/android/server/wm/DisplayAreaOrganizerTest.java
index 3220d1d..1198ee2 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayAreaOrganizerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayAreaOrganizerTest.java
@@ -16,8 +16,13 @@
 
 package com.android.server.wm;
 
+import static android.view.Display.DEFAULT_DISPLAY;
+import static android.window.DisplayAreaOrganizer.FEATURE_ROOT;
+import static android.window.DisplayAreaOrganizer.FEATURE_RUNTIME_TASK_CONTAINER_FIRST;
 import static android.window.DisplayAreaOrganizer.FEATURE_VENDOR_FIRST;
 
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.ArgumentMatchers.any;
@@ -25,6 +30,7 @@
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
+import static org.testng.Assert.assertThrows;
 
 import android.content.res.Configuration;
 import android.graphics.Rect;
@@ -55,9 +61,12 @@
 public class DisplayAreaOrganizerTest extends WindowTestsBase {
 
     private DisplayArea mTestDisplayArea;
+    private DisplayAreaOrganizerController mOrganizerController;
 
     @Before
     public void setUp() {
+        mOrganizerController =
+                mWm.mAtmService.mWindowOrganizerController.mDisplayAreaOrganizerController;
         WindowContainer parentWindow = mDisplayContent.getDefaultTaskDisplayArea().getParent();
         mTestDisplayArea = new DisplayArea(mWm, DisplayArea.Type.ANY,
                 "TestDisplayArea", FEATURE_VENDOR_FIRST);
@@ -76,8 +85,7 @@
 
     private IDisplayAreaOrganizer registerMockOrganizer(int feature, Binder binder) {
         final IDisplayAreaOrganizer organizer = createMockOrganizer(binder);
-        mWm.mAtmService.mWindowOrganizerController.mDisplayAreaOrganizerController
-                .registerOrganizer(organizer, feature);
+        mOrganizerController.registerOrganizer(organizer, feature);
         return organizer;
     }
 
@@ -87,16 +95,10 @@
         return organizer;
     }
 
-    private void unregisterMockOrganizer(IDisplayAreaOrganizer organizer) {
-        mWm.mAtmService.mWindowOrganizerController.mDisplayAreaOrganizerController
-                .unregisterOrganizer(organizer);
-    }
-
     @Test
     public void testRegisterOrganizer() throws RemoteException {
-        IDisplayAreaOrganizer organizer = createMockOrganizer(new Binder());
-        List<DisplayAreaAppearedInfo> infos = mWm.mAtmService.mWindowOrganizerController
-                .mDisplayAreaOrganizerController
+        final IDisplayAreaOrganizer organizer = createMockOrganizer(new Binder());
+        List<DisplayAreaAppearedInfo> infos = mOrganizerController
                 .registerOrganizer(organizer, FEATURE_VENDOR_FIRST).getList();
 
         // Return a list contains the DA, and no onDisplayAreaAppeared triggered.
@@ -108,16 +110,135 @@
     }
 
     @Test
+    public void testRegisterOrganizer_alreadyRegisteredFeature() {
+        registerMockOrganizer(FEATURE_VENDOR_FIRST);
+        assertThrows(IllegalStateException.class,
+                () -> registerMockOrganizer(FEATURE_VENDOR_FIRST));
+    }
+
+    @Test
+    public void testCreateTaskDisplayArea() {
+        final String newTdaName = "testTda";
+        final IDisplayAreaOrganizer organizer = createMockOrganizer(new Binder());
+        final DisplayAreaAppearedInfo tdaInfo = mOrganizerController.createTaskDisplayArea(
+                organizer, DEFAULT_DISPLAY, FEATURE_ROOT, newTdaName);
+
+        final int newTdaIndex =
+                mTestDisplayArea.getParent().mChildren.indexOf(mTestDisplayArea) + 1;
+        final WindowContainer wc = mTestDisplayArea.getParent().getChildAt(newTdaIndex);
+
+        // A new TaskDisplayArea is created on the top.
+        assertThat(wc).isInstanceOf(TaskDisplayArea.class);
+        assertThat(tdaInfo.getDisplayAreaInfo().displayId).isEqualTo(DEFAULT_DISPLAY);
+        assertThat(tdaInfo.getDisplayAreaInfo().token)
+                .isEqualTo(wc.mRemoteToken.toWindowContainerToken());
+
+        final TaskDisplayArea tda = wc.asTaskDisplayArea();
+
+        assertThat(tda.getName()).isEqualTo(newTdaName);
+        assertThat(tda.mFeatureId).isEqualTo(tdaInfo.getDisplayAreaInfo().featureId);
+        assertThat(tda.mCreatedByOrganizer).isTrue();
+        assertThat(tda.mOrganizer).isEqualTo(organizer);
+    }
+
+    @Test
+    public void testCreateTaskDisplayArea_incrementalTdaFeatureId() {
+        final String newTdaName = "testTda";
+        final IDisplayAreaOrganizer organizer = createMockOrganizer(new Binder());
+        final DisplayAreaAppearedInfo tdaInfo1 = mOrganizerController.createTaskDisplayArea(
+                organizer, DEFAULT_DISPLAY, FEATURE_ROOT, newTdaName);
+        final DisplayAreaAppearedInfo tdaInfo2 = mOrganizerController.createTaskDisplayArea(
+                organizer, DEFAULT_DISPLAY, FEATURE_ROOT, newTdaName);
+
+        // New created TDA has unique feature id starting from FEATURE_RUNTIME_TASK_CONTAINER_FIRST.
+        assertThat(tdaInfo1.getDisplayAreaInfo().featureId).isEqualTo(
+                FEATURE_RUNTIME_TASK_CONTAINER_FIRST);
+        assertThat(tdaInfo2.getDisplayAreaInfo().featureId).isEqualTo(
+                FEATURE_RUNTIME_TASK_CONTAINER_FIRST + 1);
+    }
+
+
+    @Test
+    public void testCreateTaskDisplayArea_invalidDisplayAndRoot() {
+        final IDisplayAreaOrganizer organizer = createMockOrganizer(new Binder());
+        assertThrows(IllegalArgumentException.class, () ->
+                mOrganizerController.createTaskDisplayArea(
+                        organizer, SystemServicesTestRule.sNextDisplayId + 1, FEATURE_ROOT,
+                        "testTda"));
+        assertThrows(IllegalArgumentException.class, () ->
+                mOrganizerController.createTaskDisplayArea(
+                        organizer, DEFAULT_DISPLAY, FEATURE_ROOT - 1, "testTda"));
+    }
+
+    @Test
+    public void testDeleteTaskDisplayArea() {
+        final String newTdaName = "testTda";
+        final IDisplayAreaOrganizer organizer = createMockOrganizer(new Binder());
+        final DisplayAreaAppearedInfo tdaInfo = mOrganizerController.createTaskDisplayArea(
+                organizer, DEFAULT_DISPLAY, FEATURE_ROOT, newTdaName);
+        final int tdaFeatureId = tdaInfo.getDisplayAreaInfo().featureId;
+
+        final TaskDisplayArea newTda = mDisplayContent.getItemFromDisplayAreas(
+                da -> da.mFeatureId == tdaFeatureId ? da.asTaskDisplayArea() : null);
+        spyOn(newTda);
+
+        mOrganizerController.deleteTaskDisplayArea(newTda.mRemoteToken.toWindowContainerToken());
+
+        verify(newTda).remove();
+        verify(newTda).removeImmediately();
+        assertThat(newTda.mOrganizer).isNull();
+        assertThat(newTda.isRemoved()).isTrue();
+
+        final TaskDisplayArea curTda = mDisplayContent.getItemFromDisplayAreas(
+                da -> da.mFeatureId == tdaFeatureId ? da.asTaskDisplayArea() : null);
+
+        assertThat(curTda).isNull();
+    }
+
+    @Test
+    public void testUnregisterOrganizer_deleteNewCreatedTaskDisplayArea() {
+        final String newTdaName = "testTda";
+        final IDisplayAreaOrganizer organizer = createMockOrganizer(new Binder());
+        final DisplayAreaAppearedInfo tdaInfo = mOrganizerController.createTaskDisplayArea(
+                organizer, DEFAULT_DISPLAY, FEATURE_ROOT, newTdaName);
+        final int tdaFeatureId = tdaInfo.getDisplayAreaInfo().featureId;
+
+        final TaskDisplayArea newTda = mDisplayContent.getItemFromDisplayAreas(
+                da -> da.mFeatureId == tdaFeatureId ? da.asTaskDisplayArea() : null);
+        spyOn(newTda);
+
+        mOrganizerController.unregisterOrganizer(organizer);
+
+        verify(newTda).remove();
+        verify(newTda).removeImmediately();
+        assertThat(newTda.mOrganizer).isNull();
+        assertThat(newTda.isRemoved()).isTrue();
+
+        final TaskDisplayArea curTda = mDisplayContent.getItemFromDisplayAreas(
+                da -> da.mFeatureId == tdaFeatureId ? da.asTaskDisplayArea() : null);
+
+        assertThat(curTda).isNull();
+    }
+
+    @Test
+    public void testDeleteTaskDisplayArea_invalidTaskDisplayArea() {
+        final TaskDisplayArea tda = mDisplayContent.getDefaultTaskDisplayArea();
+        assertThrows(IllegalArgumentException.class, () ->
+                mOrganizerController.deleteTaskDisplayArea(
+                        tda.mRemoteToken.toWindowContainerToken()));
+    }
+
+    @Test
     public void testAppearedVanished() throws RemoteException {
-        IDisplayAreaOrganizer organizer = registerMockOrganizer(FEATURE_VENDOR_FIRST);
-        unregisterMockOrganizer(organizer);
+        final IDisplayAreaOrganizer organizer = registerMockOrganizer(FEATURE_VENDOR_FIRST);
+        mOrganizerController.unregisterOrganizer(organizer);
 
         verify(organizer).onDisplayAreaVanished(any());
     }
 
     @Test
     public void testChanged() throws RemoteException {
-        IDisplayAreaOrganizer organizer = registerMockOrganizer(FEATURE_VENDOR_FIRST);
+        final IDisplayAreaOrganizer organizer = registerMockOrganizer(FEATURE_VENDOR_FIRST);
         mDisplayContent.setBounds(new Rect(0, 0, 1000, 1000));
 
         verify(organizer).onDisplayAreaInfoChanged(any());
@@ -137,7 +258,7 @@
 
         assertThat(mTestDisplayArea.mOrganizer).isNotNull();
 
-        unregisterMockOrganizer(createMockOrganizer(binder));
+        mOrganizerController.unregisterOrganizer(createMockOrganizer(binder));
 
         assertThat(mTestDisplayArea.mOrganizer).isNull();
     }