From dfeeea1b8287199034b884e4ca9ba609529636ef Mon Sep 17 00:00:00 2001 From: Chris Li Date: Mon, 16 Nov 2020 20:46:13 -0800 Subject: [PATCH] Add API to create new TaskDisplayArea at runtime Allow DA organizer to create new TaskDisplayArea. Fix: 173455508 Test: atest WmTests:DisplayAreaOrganizerTest Change-Id: I7d4e88f43ae14561720c6942ca0e9f82dc28e9f6 --- .../android/window/DisplayAreaOrganizer.java | 57 +++++++ .../IDisplayAreaOrganizerController.aidl | 25 +++ data/etc/services.core.protolog.json | 24 +-- .../com/android/server/wm/DisplayArea.java | 7 + .../wm/DisplayAreaOrganizerController.java | 152 +++++++++++++++++- .../server/wm/DisplayAreaPolicyBuilder.java | 6 + .../android/server/wm/RootDisplayArea.java | 5 + .../android/server/wm/TaskDisplayArea.java | 17 ++ .../android/server/wm/WindowContainer.java | 42 ++++- .../server/wm/DisplayAreaOrganizerTest.java | 149 +++++++++++++++-- 10 files changed, 447 insertions(+), 37 deletions(-) diff --git a/core/java/android/window/DisplayAreaOrganizer.java b/core/java/android/window/DisplayAreaOrganizer.java index 6e20452ad0615..1ac188cf2486a 100644 --- a/core/java/android/window/DisplayAreaOrganizer.java +++ b/core/java/android/window/DisplayAreaOrganizer.java @@ -100,6 +100,19 @@ public class DisplayAreaOrganizer extends WindowOrganizer { */ 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. @@ -131,6 +144,50 @@ public class DisplayAreaOrganizer extends WindowOrganizer { } } + /** + * 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 diff --git a/core/java/android/window/IDisplayAreaOrganizerController.aidl b/core/java/android/window/IDisplayAreaOrganizerController.aidl index edabcf8ad0de5..26fa434506cf8 100644 --- a/core/java/android/window/IDisplayAreaOrganizerController.aidl +++ b/core/java/android/window/IDisplayAreaOrganizerController.aidl @@ -19,6 +19,7 @@ package android.window; import android.content.pm.ParceledListSlice; import android.window.DisplayAreaAppearedInfo; import android.window.IDisplayAreaOrganizer; +import android.window.WindowContainerToken; /** @hide */ interface IDisplayAreaOrganizerController { @@ -37,4 +38,28 @@ interface IDisplayAreaOrganizerController { * 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 52da707565d7a..10310237f89b8 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 8ad2958f9a767..48e030050b077 100644 --- a/services/core/java/com/android/server/wm/DisplayArea.java +++ b/services/core/java/com/android/server/wm/DisplayArea.java @@ -380,6 +380,13 @@ public class DisplayArea extends WindowContainer { return result; } + @Nullable + @Override + R getItemFromDisplayAreas(Function callback) { + final R item = super.getItemFromDisplayAreas(callback); + return item != null ? item : callback.apply(this); + } + @Nullable @Override R getItemFromTaskDisplayAreas(Function callback, diff --git a/services/core/java/com/android/server/wm/DisplayAreaOrganizerController.java b/services/core/java/com/android/server/wm/DisplayAreaOrganizerController.java index 43b9a218d072d..c475da354dda4 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.view.SurfaceControl; 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 @@ import java.util.List; 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 mOrganizersByFeatureIds = new HashMap(); @@ -92,10 +102,8 @@ public class DisplayAreaOrganizerController extends IDisplayAreaOrganizerControl final List 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 @@ public class DisplayAreaOrganizerController extends IDisplayAreaOrganizerControl } } + @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 @@ public class DisplayAreaOrganizerController extends IDisplayAreaOrganizerControl 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 80ec722bc274e..6a420874b924e 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 @@ class DisplayAreaPolicyBuilder { throw new IllegalStateException("Root must be set for the display area policy."); } + final Set 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 1e5d045e8680f..da04f438a496b 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 @@ class RootDisplayArea extends DisplayArea { 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 c02e7ad5b7c6d..81b8200aa2b48 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 @@ final class TaskDisplayArea extends DisplayArea { */ 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; } /** @@ -1913,6 +1925,11 @@ final class TaskDisplayArea extends DisplayArea { return true; } + @Override + TaskDisplayArea asTaskDisplayArea() { + return this; + } + @Override void dump(PrintWriter pw, String prefix, boolean dumpAll) { pw.println(prefix + "TaskDisplayArea " + getName()); diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java index 4574be70674f6..06449c69f1bee 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 @@ class WindowContainer extends ConfigurationContainer< /** * 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 @@ class WindowContainer extends ConfigurationContainer< * 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 @@ class WindowContainer extends ConfigurationContainer< * 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 @@ class WindowContainer extends ConfigurationContainer< * 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. @@ -1910,11 +1910,31 @@ class WindowContainer extends ConfigurationContainer< return reduceOnAllTaskDisplayAreas(accumulator, initValue, true /* traverseTopToBottom */); } + /** + * 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 getItemFromDisplayAreas(Function 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 @@ class WindowContainer extends ConfigurationContainer< * {@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 @@ class WindowContainer extends ConfigurationContainer< 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 3220d1d6a990a..1198ee2ba5f41 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.mock; 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 @@ import java.util.List; 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 @@ public class DisplayAreaOrganizerTest extends WindowTestsBase { 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 @@ public class DisplayAreaOrganizerTest extends WindowTestsBase { 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 infos = mWm.mAtmService.mWindowOrganizerController - .mDisplayAreaOrganizerController + final IDisplayAreaOrganizer organizer = createMockOrganizer(new Binder()); + List infos = mOrganizerController .registerOrganizer(organizer, FEATURE_VENDOR_FIRST).getList(); // Return a list contains the DA, and no onDisplayAreaAppeared triggered. @@ -107,17 +109,136 @@ public class DisplayAreaOrganizerTest extends WindowTestsBase { any(SurfaceControl.class)); } + @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 @@ public class DisplayAreaOrganizerTest extends WindowTestsBase { assertThat(mTestDisplayArea.mOrganizer).isNotNull(); - unregisterMockOrganizer(createMockOrganizer(binder)); + mOrganizerController.unregisterOrganizer(createMockOrganizer(binder)); assertThat(mTestDisplayArea.mOrganizer).isNull(); }