Introduce WindowOrganizer

WM currently only allows the organization of tasks, however we will soon
be allowing the organization of DisplayAreas. To help with the code
structure, we are introducing WindowOrganizer interface which will
contain common APIs for all types of windows organizers (e.g.
applyTransaction) and also be the interfaece for getting the controller
for other organizers.

Test: they pass!
Bug: 147406652
Bug: 152113464
Bug: 152117221

Change-Id: Id2797b288ce92430206c25345d60e7b0e3be98c8
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 1844395..f7278e7 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -230,6 +230,7 @@
 import android.view.IRecentsAnimationRunner;
 import android.view.RemoteAnimationAdapter;
 import android.view.RemoteAnimationDefinition;
+import android.window.IWindowOrganizerController;
 import android.window.WindowContainerTransaction;
 import android.view.WindowManager;
 
@@ -663,10 +664,8 @@
 
     private FontScaleSettingObserver mFontScaleSettingObserver;
 
-    /**
-     * Stores the registration and state of TaskOrganizers in use.
-     */
-    TaskOrganizerController mTaskOrganizerController = new TaskOrganizerController(this);
+    WindowOrganizerController mWindowOrganizerController;
+    TaskOrganizerController mTaskOrganizerController;
 
     private int mDeviceOwnerUid = Process.INVALID_UID;
 
@@ -724,6 +723,8 @@
         mLifecycleManager = new ClientLifecycleManager();
         mInternal = new LocalService();
         GL_ES_VERSION = SystemProperties.getInt("ro.opengles.version", GL_ES_VERSION_UNDEFINED);
+        mWindowOrganizerController = new WindowOrganizerController(this);
+        mTaskOrganizerController = mWindowOrganizerController.mTaskOrganizerController;
     }
 
     public void onSystemReady() {
@@ -2772,7 +2773,7 @@
         }
         WindowContainerTransaction wct = new WindowContainerTransaction();
         wct.reparent(stack.mRemoteToken, tile.mRemoteToken, toTop);
-        mTaskOrganizerController.applyContainerTransaction(wct, null);
+        mWindowOrganizerController.applyTransaction(wct);
     }
 
     /**
@@ -4311,7 +4312,7 @@
                     }
                 }
                 wct.setBounds(secondary.mRemoteToken, otherRect);
-                mTaskOrganizerController.applyContainerTransaction(wct, null /* organizer */);
+                mWindowOrganizerController.applyTransaction(wct);
             }
         } finally {
             Binder.restoreCallingIdentity(ident);
@@ -4332,10 +4333,10 @@
     }
 
     @Override
-    public ITaskOrganizerController getTaskOrganizerController() {
+    public IWindowOrganizerController getWindowOrganizerController() {
         mAmInternal.enforceCallingPermission(MANAGE_ACTIVITY_STACKS,
-                "getTaskOrganizerController()");
-        return mTaskOrganizerController;
+                "getWindowOrganizerController()");
+        return mWindowOrganizerController;
     }
 
     /**
diff --git a/services/core/java/com/android/server/wm/DisplayRotation.java b/services/core/java/com/android/server/wm/DisplayRotation.java
index 776a986..60b817c 100644
--- a/services/core/java/com/android/server/wm/DisplayRotation.java
+++ b/services/core/java/com/android/server/wm/DisplayRotation.java
@@ -531,8 +531,7 @@
             try {
                 mDisplayContent.sendNewConfiguration();
                 if (t != null) {
-                    mService.mAtmService.mTaskOrganizerController.applyContainerTransaction(t,
-                            null /* organizer */);
+                    mService.mAtmService.mWindowOrganizerController.applyTransaction(t);
                 }
             } finally {
                 mService.mAtmService.continueWindowLayout();
diff --git a/services/core/java/com/android/server/wm/TaskOrganizerController.java b/services/core/java/com/android/server/wm/TaskOrganizerController.java
index 9bf4d63..fc58ee7 100644
--- a/services/core/java/com/android/server/wm/TaskOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskOrganizerController.java
@@ -22,59 +22,34 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
 import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
 
-import static com.android.server.wm.ActivityStackSupervisor.PRESERVE_WINDOWS;
-import static com.android.server.wm.Task.FLAG_FORCE_HIDDEN_FOR_TASK_ORG;
-import static com.android.server.wm.WindowContainer.POSITION_BOTTOM;
-import static com.android.server.wm.WindowContainer.POSITION_TOP;
+import static com.android.server.wm.WindowOrganizerController.CONTROLLABLE_CONFIGS;
+import static com.android.server.wm.WindowOrganizerController.CONTROLLABLE_WINDOW_CONFIGS;
 
 import android.annotation.Nullable;
 import android.app.ActivityManager.RunningTaskInfo;
-import android.app.WindowConfiguration;
 import android.content.pm.ActivityInfo;
-import android.content.res.Configuration;
-import android.graphics.Rect;
 import android.os.Binder;
 import android.os.IBinder;
 import android.os.RemoteException;
-import android.util.ArraySet;
 import android.util.Slog;
-import android.view.SurfaceControl;
 import android.window.ITaskOrganizerController;
 import android.window.ITaskOrganizer;
 import android.window.IWindowContainer;
-import android.window.WindowContainerTransaction;
 
 import com.android.internal.util.ArrayUtils;
-import com.android.internal.util.function.pooled.PooledConsumer;
-import com.android.internal.util.function.pooled.PooledLambda;
 
 import java.util.ArrayList;
 import java.util.HashMap;
-import java.util.Iterator;
 import java.util.List;
-import java.util.Map;
 import java.util.WeakHashMap;
 
 /**
  * Stores the TaskOrganizers associated with a given windowing mode and
  * their associated state.
  */
-class TaskOrganizerController extends ITaskOrganizerController.Stub
-    implements BLASTSyncEngine.TransactionReadyListener {
+class TaskOrganizerController extends ITaskOrganizerController.Stub {
     private static final String TAG = "TaskOrganizerController";
 
-    /** Flag indicating that an applied transaction may have effected lifecycle */
-    private static final int TRANSACT_EFFECTS_CLIENT_CONFIG = 1;
-    private static final int TRANSACT_EFFECTS_LIFECYCLE = 1 << 1;
-
-    /**
-     * Masks specifying which configurations task-organizers can control. Incoming transactions
-     * will be filtered to only include these.
-     */
-    private static final int CONTROLLABLE_CONFIGS = ActivityInfo.CONFIG_WINDOW_CONFIGURATION
-                | ActivityInfo.CONFIG_SMALLEST_SCREEN_SIZE | ActivityInfo.CONFIG_SCREEN_SIZE;
-    private static final int CONTROLLABLE_WINDOW_CONFIGS = WindowConfiguration.WINDOW_CONFIG_BOUNDS
-                | WindowConfiguration.WINDOW_CONFIG_APP_BOUNDS;
     /**
      * Masks specifying which configurations are important to report back to an organizer when
      * changed.
@@ -181,13 +156,9 @@
     final HashMap<Integer, TaskOrganizerState> mTaskOrganizersForWindowingMode = new HashMap();
     final HashMap<IBinder, TaskOrganizerState> mTaskOrganizerStates = new HashMap();
 
-    final HashMap<Integer, ITaskOrganizer> mTaskOrganizersByPendingSyncId = new HashMap();
-
     private final WeakHashMap<Task, RunningTaskInfo> mLastSentTaskInfos = new WeakHashMap<>();
     private final ArrayList<Task> mPendingTaskInfoChanges = new ArrayList<>();
 
-    private final BLASTSyncEngine mBLASTSyncEngine = new BLASTSyncEngine();
-
     final ActivityTaskManagerService mService;
 
     RunningTaskInfo mTmpTaskInfo;
@@ -498,253 +469,4 @@
             Binder.restoreCallingIdentity(ident);
         }
     }
-
-    private int sanitizeAndApplyChange(WindowContainer container,
-            WindowContainerTransaction.Change change) {
-        if (!(container instanceof Task)) {
-            throw new RuntimeException("Invalid token in task transaction");
-        }
-        final Task task = (Task) container;
-        // The "client"-facing API should prevent bad changes; however, just in case, sanitize
-        // masks here.
-        final int configMask = change.getConfigSetMask() & CONTROLLABLE_CONFIGS;
-        final int windowMask = change.getWindowSetMask() & CONTROLLABLE_WINDOW_CONFIGS;
-        int effects = 0;
-        if (configMask != 0) {
-            Configuration c = new Configuration(container.getRequestedOverrideConfiguration());
-            c.setTo(change.getConfiguration(), configMask, windowMask);
-            container.onRequestedOverrideConfigurationChanged(c);
-            // TODO(b/145675353): remove the following once we could apply new bounds to the
-            // pinned stack together with its children.
-            resizePinnedStackIfNeeded(container, configMask, windowMask, c);
-            effects |= TRANSACT_EFFECTS_CLIENT_CONFIG;
-        }
-        if ((change.getChangeMask() & WindowContainerTransaction.Change.CHANGE_FOCUSABLE) != 0) {
-            if (container.setFocusable(change.getFocusable())) {
-                effects |= TRANSACT_EFFECTS_LIFECYCLE;
-            }
-        }
-        if ((change.getChangeMask() & WindowContainerTransaction.Change.CHANGE_HIDDEN) != 0) {
-            if (task.setForceHidden(FLAG_FORCE_HIDDEN_FOR_TASK_ORG, change.getHidden())) {
-                effects |= TRANSACT_EFFECTS_LIFECYCLE;
-            }
-        }
-        return effects;
-    }
-
-    private int sanitizeAndApplyHierarchyOp(WindowContainer container,
-            WindowContainerTransaction.HierarchyOp hop) {
-        if (!(container instanceof Task)) {
-            throw new IllegalArgumentException("Invalid container in hierarchy op");
-        }
-        if (container.getDisplayContent() == null) {
-            Slog.w(TAG, "Container is no longer attached: " + container);
-            return 0;
-        }
-        if (hop.isReparent()) {
-            // special case for tiles since they are "virtual" parents
-            if (container instanceof ActivityStack && ((ActivityStack) container).isRootTask()) {
-                ActivityStack as = (ActivityStack) container;
-                TaskTile newParent = hop.getNewParent() == null ? null
-                        : (TaskTile) WindowContainer.fromBinder(hop.getNewParent());
-                if (as.getTile() != newParent) {
-                    if (as.getTile() != null) {
-                        as.getTile().removeChild(as);
-                    }
-                    if (newParent != null) {
-                        if (!as.affectedBySplitScreenResize()) {
-                            return 0;
-                        }
-                        newParent.addChild(as, POSITION_TOP);
-                    }
-                }
-                if (hop.getToTop()) {
-                    as.getDisplay().positionStackAtTop(as, false /* includingParents */);
-                } else {
-                    as.getDisplay().positionStackAtBottom(as);
-                }
-            } else if (container instanceof Task) {
-                throw new RuntimeException("Reparenting leaf Tasks is not supported now.");
-            }
-        } else {
-            // Ugh, of course ActivityStack has its own special reorder logic...
-            if (container instanceof ActivityStack && ((ActivityStack) container).isRootTask()) {
-                ActivityStack as = (ActivityStack) container;
-                if (hop.getToTop()) {
-                    as.getDisplay().positionStackAtTop(as, false /* includingParents */);
-                } else {
-                    as.getDisplay().positionStackAtBottom(as);
-                }
-            } else {
-                container.getParent().positionChildAt(
-                        hop.getToTop() ? POSITION_TOP : POSITION_BOTTOM,
-                        container, false /* includingParents */);
-            }
-        }
-        return TRANSACT_EFFECTS_LIFECYCLE;
-    }
-
-    private void resizePinnedStackIfNeeded(ConfigurationContainer container, int configMask,
-            int windowMask, Configuration config) {
-        if ((container instanceof ActivityStack)
-                && ((configMask & ActivityInfo.CONFIG_WINDOW_CONFIGURATION) != 0)
-                && ((windowMask & WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0)) {
-            final ActivityStack stack = (ActivityStack) container;
-            if (stack.inPinnedWindowingMode()) {
-                stack.resize(config.windowConfiguration.getBounds(),
-                        null /* configBounds */, PRESERVE_WINDOWS, true /* deferResume */);
-            }
-        }
-    }
-
-    private int applyWindowContainerChange(WindowContainer wc,
-            WindowContainerTransaction.Change c) {
-        int effects = sanitizeAndApplyChange(wc, c);
-
-        final Task tr = wc.asTask();
-
-        final SurfaceControl.Transaction t = c.getBoundsChangeTransaction();
-        if (t != null) {
-            tr.setMainWindowSizeChangeTransaction(t);
-        }
-
-        Rect enterPipBounds = c.getEnterPipBounds();
-        if (enterPipBounds != null) {
-            mService.mStackSupervisor.updatePictureInPictureMode(tr,
-                    enterPipBounds, true);
-        }
-
-        final int windowingMode = c.getWindowingMode();
-        if (windowingMode > -1) {
-            tr.setWindowingMode(windowingMode);
-        }
-        final int childWindowingMode = c.getActivityWindowingMode();
-        if (childWindowingMode > -1) {
-            tr.setActivityWindowingMode(childWindowingMode);
-        }
-
-        return effects;
-    }
-
-    @Override
-    public int applyContainerTransaction(WindowContainerTransaction t, ITaskOrganizer organizer) {
-        enforceStackPermission("applyContainerTransaction()");
-        int syncId = -1;
-        if (t == null) {
-            throw new IllegalArgumentException(
-                    "Null transaction passed to applyContainerTransaction");
-        }
-        long ident = Binder.clearCallingIdentity();
-        try {
-            synchronized (mGlobalLock) {
-                int effects = 0;
-
-                /**
-                 * If organizer is non-null we are looking to synchronize this transaction
-                 * by collecting all the results in to a SurfaceFlinger transaction and
-                 * then delivering that to the given organizers transaction ready callback.
-                 * See {@link BLASTSyncEngine} for the details of the operation. But at
-                 * a high level we create a sync operation with a given ID and an associated
-                 * organizer. Then we notify each WindowContainer in this WindowContainer
-                 * transaction that it is participating in a sync operation with that
-                 * ID. Once everything is notified we tell the BLASTSyncEngine
-                 * "setSyncReady" which means that we have added everything
-                 * to the set. At any point after this, all the WindowContainers
-                 * will eventually finish applying their changes and notify the
-                 * BLASTSyncEngine which will deliver the Transaction to the organizer.
-                 */
-                if (organizer != null) {
-                    syncId = startSyncWithOrganizer(organizer);
-                }
-                mService.deferWindowLayout();
-                try {
-                    ArraySet<WindowContainer> haveConfigChanges = new ArraySet<>();
-                    Iterator<Map.Entry<IBinder, WindowContainerTransaction.Change>> entries =
-                            t.getChanges().entrySet().iterator();
-                    while (entries.hasNext()) {
-                        final Map.Entry<IBinder, WindowContainerTransaction.Change> entry =
-                                entries.next();
-                        final WindowContainer wc = WindowContainer.fromBinder(entry.getKey());
-                        int containerEffect = applyWindowContainerChange(wc, entry.getValue());
-                        effects |= containerEffect;
-
-                        // Lifecycle changes will trigger ensureConfig for everything.
-                        if ((effects & TRANSACT_EFFECTS_LIFECYCLE) == 0
-                                && (containerEffect & TRANSACT_EFFECTS_CLIENT_CONFIG) != 0) {
-                            haveConfigChanges.add(wc);
-                        }
-                        if (syncId >= 0) {
-                            mBLASTSyncEngine.addToSyncSet(syncId, wc);
-                        }
-                    }
-                    // Hierarchy changes
-                    final List<WindowContainerTransaction.HierarchyOp> hops = t.getHierarchyOps();
-                    for (int i = 0, n = hops.size(); i < n; ++i) {
-                        final WindowContainerTransaction.HierarchyOp hop = hops.get(i);
-                        final WindowContainer wc = WindowContainer.fromBinder(hop.getContainer());
-                        effects |= sanitizeAndApplyHierarchyOp(wc, hop);
-                    }
-                    if ((effects & TRANSACT_EFFECTS_LIFECYCLE) != 0) {
-                        // Already calls ensureActivityConfig
-                        mService.mRootWindowContainer.ensureActivitiesVisible(
-                                null, 0, PRESERVE_WINDOWS);
-                    } else if ((effects & TRANSACT_EFFECTS_CLIENT_CONFIG) != 0) {
-                        final PooledConsumer f = PooledLambda.obtainConsumer(
-                                ActivityRecord::ensureActivityConfiguration,
-                                PooledLambda.__(ActivityRecord.class), 0,
-                                false /* preserveWindow */);
-                        try {
-                            for (int i = haveConfigChanges.size() - 1; i >= 0; --i) {
-                                final WindowContainer wc = haveConfigChanges.valueAt(i);
-                                final Task task = wc.asTask();
-                                final TaskTile tile = task != null ? task.asTile() : null;
-                                if (tile != null) {
-                                    // Special case for tile. Can't override normal forAllActivities
-                                    // because it generates duplicate calls and messes up existing
-                                    // code-paths.
-                                    tile.forAllTileActivities(f);
-                                } else {
-                                    wc.forAllActivities(f);
-                                }
-                            }
-                        } finally {
-                            f.recycle();
-                        }
-                    }
-                } finally {
-                    mService.continueWindowLayout();
-                    if (syncId >= 0) {
-                        setSyncReady(syncId);
-                    }
-                }
-            }
-        } finally {
-            Binder.restoreCallingIdentity(ident);
-        }
-        return syncId;
-    }
-
-    @Override
-    public void transactionReady(int id, SurfaceControl.Transaction sc) {
-        final ITaskOrganizer organizer = mTaskOrganizersByPendingSyncId.get(id);
-        if (organizer == null) {
-            Slog.e(TAG, "Got transaction complete for unexpected ID");
-        }
-        try {
-            organizer.transactionReady(id, sc);
-        } catch (RemoteException e) {
-        }
-
-        mTaskOrganizersByPendingSyncId.remove(id);
-    }
-
-    int startSyncWithOrganizer(ITaskOrganizer organizer) {
-        int id = mBLASTSyncEngine.startSyncSet(this);
-        mTaskOrganizersByPendingSyncId.put(id, organizer);
-        return id;
-    }
-
-    void setSyncReady(int id) {
-        mBLASTSyncEngine.setReady(id);
-    }
 }
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
new file mode 100644
index 0000000..ebfe109
--- /dev/null
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -0,0 +1,347 @@
+/*
+ * 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.Manifest.permission.MANAGE_ACTIVITY_STACKS;
+
+import static com.android.server.wm.ActivityStackSupervisor.PRESERVE_WINDOWS;
+import static com.android.server.wm.Task.FLAG_FORCE_HIDDEN_FOR_TASK_ORG;
+import static com.android.server.wm.WindowContainer.POSITION_BOTTOM;
+import static com.android.server.wm.WindowContainer.POSITION_TOP;
+
+import android.app.WindowConfiguration;
+import android.content.pm.ActivityInfo;
+import android.content.res.Configuration;
+import android.graphics.Rect;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.ArraySet;
+import android.util.Slog;
+import android.view.SurfaceControl;
+import android.window.ITaskOrganizerController;
+import android.window.IWindowContainerTransactionCallback;
+import android.window.IWindowOrganizerController;
+import android.window.WindowContainerTransaction;
+
+import com.android.internal.util.function.pooled.PooledConsumer;
+import com.android.internal.util.function.pooled.PooledLambda;
+
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Server side implementation for the interface for organizing windows
+ * @see android.window.WindowOrganizer
+ */
+class WindowOrganizerController extends IWindowOrganizerController.Stub
+    implements BLASTSyncEngine.TransactionReadyListener {
+
+    private static final String TAG = "WindowOrganizerController";
+
+    /** Flag indicating that an applied transaction may have effected lifecycle */
+    private static final int TRANSACT_EFFECTS_CLIENT_CONFIG = 1;
+    private static final int TRANSACT_EFFECTS_LIFECYCLE = 1 << 1;
+
+    /**
+     * Masks specifying which configurations task-organizers can control. Incoming transactions
+     * will be filtered to only include these.
+     */
+    static final int CONTROLLABLE_CONFIGS = ActivityInfo.CONFIG_WINDOW_CONFIGURATION
+            | ActivityInfo.CONFIG_SMALLEST_SCREEN_SIZE | ActivityInfo.CONFIG_SCREEN_SIZE;
+    static final int CONTROLLABLE_WINDOW_CONFIGS = WindowConfiguration.WINDOW_CONFIG_BOUNDS
+            | WindowConfiguration.WINDOW_CONFIG_APP_BOUNDS;
+
+    private final ActivityTaskManagerService mService;
+    private final WindowManagerGlobalLock mGlobalLock;
+
+    private final BLASTSyncEngine mBLASTSyncEngine = new BLASTSyncEngine();
+    private final HashMap<Integer, IWindowContainerTransactionCallback>
+            mTransactionCallbacksByPendingSyncId = new HashMap();
+
+    final TaskOrganizerController mTaskOrganizerController;
+
+    WindowOrganizerController(ActivityTaskManagerService atm) {
+        mService = atm;
+        mGlobalLock = atm.mGlobalLock;
+        mTaskOrganizerController = new TaskOrganizerController(mService);
+    }
+
+    @Override
+    public void applyTransaction(WindowContainerTransaction t) {
+        applySyncTransaction(t, null /*callback*/);
+    }
+
+    @Override
+    public int applySyncTransaction(WindowContainerTransaction t,
+            IWindowContainerTransactionCallback callback) {
+        enforceStackPermission("applySyncTransaction()");
+        int syncId = -1;
+        if (t == null) {
+            throw new IllegalArgumentException(
+                    "Null transaction passed to applySyncTransaction");
+        }
+        long ident = Binder.clearCallingIdentity();
+        try {
+            synchronized (mGlobalLock) {
+                int effects = 0;
+
+                /**
+                 * If callback is non-null we are looking to synchronize this transaction by
+                 * collecting all the results in to a SurfaceFlinger transaction and then delivering
+                 * that to the given transaction ready callback. See {@link BLASTSyncEngine} for the
+                 * details of the operation. But at a high level we create a sync operation with a
+                 * given ID and an associated callback. Then we notify each WindowContainer in this
+                 * WindowContainer transaction that it is participating in a sync operation with
+                 * that ID. Once everything is notified we tell the BLASTSyncEngine "setSyncReady"
+                 * which means that we have added everything to the set. At any point after this,
+                 * all the WindowContainers will eventually finish applying their changes and notify
+                 * the BLASTSyncEngine which will deliver the Transaction to the callback.
+                 */
+                if (callback != null) {
+                    syncId = startSyncWithOrganizer(callback);
+                }
+                mService.deferWindowLayout();
+                try {
+                    ArraySet<WindowContainer> haveConfigChanges = new ArraySet<>();
+                    Iterator<Map.Entry<IBinder, WindowContainerTransaction.Change>> entries =
+                            t.getChanges().entrySet().iterator();
+                    while (entries.hasNext()) {
+                        final Map.Entry<IBinder, WindowContainerTransaction.Change> entry =
+                                entries.next();
+                        final WindowContainer wc = WindowContainer.fromBinder(entry.getKey());
+                        int containerEffect = applyWindowContainerChange(wc, entry.getValue());
+                        effects |= containerEffect;
+
+                        // Lifecycle changes will trigger ensureConfig for everything.
+                        if ((effects & TRANSACT_EFFECTS_LIFECYCLE) == 0
+                                && (containerEffect & TRANSACT_EFFECTS_CLIENT_CONFIG) != 0) {
+                            haveConfigChanges.add(wc);
+                        }
+                        if (syncId >= 0) {
+                            mBLASTSyncEngine.addToSyncSet(syncId, wc);
+                        }
+                    }
+                    // Hierarchy changes
+                    final List<WindowContainerTransaction.HierarchyOp> hops = t.getHierarchyOps();
+                    for (int i = 0, n = hops.size(); i < n; ++i) {
+                        final WindowContainerTransaction.HierarchyOp hop = hops.get(i);
+                        final WindowContainer wc = WindowContainer.fromBinder(hop.getContainer());
+                        effects |= sanitizeAndApplyHierarchyOp(wc, hop);
+                    }
+                    if ((effects & TRANSACT_EFFECTS_LIFECYCLE) != 0) {
+                        // Already calls ensureActivityConfig
+                        mService.mRootWindowContainer.ensureActivitiesVisible(
+                                null, 0, PRESERVE_WINDOWS);
+                    } else if ((effects & TRANSACT_EFFECTS_CLIENT_CONFIG) != 0) {
+                        final PooledConsumer f = PooledLambda.obtainConsumer(
+                                ActivityRecord::ensureActivityConfiguration,
+                                PooledLambda.__(ActivityRecord.class), 0,
+                                false /* preserveWindow */);
+                        try {
+                            for (int i = haveConfigChanges.size() - 1; i >= 0; --i) {
+                                final WindowContainer wc = haveConfigChanges.valueAt(i);
+                                final Task task = wc.asTask();
+                                final TaskTile tile = task != null ? task.asTile() : null;
+                                if (tile != null) {
+                                    // Special case for tile. Can't override normal forAllActivities
+                                    // because it generates duplicate calls and messes up existing
+                                    // code-paths.
+                                    tile.forAllTileActivities(f);
+                                } else {
+                                    wc.forAllActivities(f);
+                                }
+                            }
+                        } finally {
+                            f.recycle();
+                        }
+                    }
+                } finally {
+                    mService.continueWindowLayout();
+                    if (syncId >= 0) {
+                        setSyncReady(syncId);
+                    }
+                }
+            }
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+        return syncId;
+    }
+
+    private int sanitizeAndApplyChange(WindowContainer container,
+            WindowContainerTransaction.Change change) {
+        if (!(container instanceof Task)) {
+            throw new RuntimeException("Invalid token in task transaction");
+        }
+        final Task task = (Task) container;
+        // The "client"-facing API should prevent bad changes; however, just in case, sanitize
+        // masks here.
+        final int configMask = change.getConfigSetMask() & CONTROLLABLE_CONFIGS;
+        final int windowMask = change.getWindowSetMask() & CONTROLLABLE_WINDOW_CONFIGS;
+        int effects = 0;
+        if (configMask != 0) {
+            Configuration c = new Configuration(container.getRequestedOverrideConfiguration());
+            c.setTo(change.getConfiguration(), configMask, windowMask);
+            container.onRequestedOverrideConfigurationChanged(c);
+            // TODO(b/145675353): remove the following once we could apply new bounds to the
+            // pinned stack together with its children.
+            resizePinnedStackIfNeeded(container, configMask, windowMask, c);
+            effects |= TRANSACT_EFFECTS_CLIENT_CONFIG;
+        }
+        if ((change.getChangeMask() & WindowContainerTransaction.Change.CHANGE_FOCUSABLE) != 0) {
+            if (container.setFocusable(change.getFocusable())) {
+                effects |= TRANSACT_EFFECTS_LIFECYCLE;
+            }
+        }
+        if ((change.getChangeMask() & WindowContainerTransaction.Change.CHANGE_HIDDEN) != 0) {
+            if (task.setForceHidden(FLAG_FORCE_HIDDEN_FOR_TASK_ORG, change.getHidden())) {
+                effects |= TRANSACT_EFFECTS_LIFECYCLE;
+            }
+        }
+        return effects;
+    }
+
+    private int sanitizeAndApplyHierarchyOp(WindowContainer container,
+            WindowContainerTransaction.HierarchyOp hop) {
+        if (!(container instanceof Task)) {
+            throw new IllegalArgumentException("Invalid container in hierarchy op");
+        }
+        if (container.getDisplayContent() == null) {
+            Slog.w(TAG, "Container is no longer attached: " + container);
+            return 0;
+        }
+        if (hop.isReparent()) {
+            // special case for tiles since they are "virtual" parents
+            if (container instanceof ActivityStack && ((ActivityStack) container).isRootTask()) {
+                ActivityStack as = (ActivityStack) container;
+                TaskTile newParent = hop.getNewParent() == null ? null
+                        : (TaskTile) WindowContainer.fromBinder(hop.getNewParent());
+                if (as.getTile() != newParent) {
+                    if (as.getTile() != null) {
+                        as.getTile().removeChild(as);
+                    }
+                    if (newParent != null) {
+                        if (!as.affectedBySplitScreenResize()) {
+                            return 0;
+                        }
+                        newParent.addChild(as, POSITION_TOP);
+                    }
+                }
+                if (hop.getToTop()) {
+                    as.getDisplay().positionStackAtTop(as, false /* includingParents */);
+                } else {
+                    as.getDisplay().positionStackAtBottom(as);
+                }
+            } else if (container instanceof Task) {
+                throw new RuntimeException("Reparenting leaf Tasks is not supported now.");
+            }
+        } else {
+            // Ugh, of course ActivityStack has its own special reorder logic...
+            if (container instanceof ActivityStack && ((ActivityStack) container).isRootTask()) {
+                ActivityStack as = (ActivityStack) container;
+                if (hop.getToTop()) {
+                    as.getDisplay().positionStackAtTop(as, false /* includingParents */);
+                } else {
+                    as.getDisplay().positionStackAtBottom(as);
+                }
+            } else {
+                container.getParent().positionChildAt(
+                        hop.getToTop() ? POSITION_TOP : POSITION_BOTTOM,
+                        container, false /* includingParents */);
+            }
+        }
+        return TRANSACT_EFFECTS_LIFECYCLE;
+    }
+
+    private int applyWindowContainerChange(WindowContainer wc,
+            WindowContainerTransaction.Change c) {
+        int effects = sanitizeAndApplyChange(wc, c);
+
+        final Task tr = wc.asTask();
+
+        final SurfaceControl.Transaction t = c.getBoundsChangeTransaction();
+        if (t != null) {
+            tr.setMainWindowSizeChangeTransaction(t);
+        }
+
+        Rect enterPipBounds = c.getEnterPipBounds();
+        if (enterPipBounds != null) {
+            mService.mStackSupervisor.updatePictureInPictureMode(tr,
+                    enterPipBounds, true);
+        }
+
+        final int windowingMode = c.getWindowingMode();
+        if (windowingMode > -1) {
+            tr.setWindowingMode(windowingMode);
+        }
+        final int childWindowingMode = c.getActivityWindowingMode();
+        if (childWindowingMode > -1) {
+            tr.setActivityWindowingMode(childWindowingMode);
+        }
+
+        return effects;
+    }
+
+    private void resizePinnedStackIfNeeded(ConfigurationContainer container, int configMask,
+            int windowMask, Configuration config) {
+        if ((container instanceof ActivityStack)
+                && ((configMask & ActivityInfo.CONFIG_WINDOW_CONFIGURATION) != 0)
+                && ((windowMask & WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0)) {
+            final ActivityStack stack = (ActivityStack) container;
+            if (stack.inPinnedWindowingMode()) {
+                stack.resize(config.windowConfiguration.getBounds(),
+                        null /* configBounds */, PRESERVE_WINDOWS, true /* deferResume */);
+            }
+        }
+    }
+
+    @Override
+    public ITaskOrganizerController getTaskOrganizerController() {
+        enforceStackPermission("getTaskOrganizerController()");
+        return mTaskOrganizerController;
+    }
+
+    int startSyncWithOrganizer(IWindowContainerTransactionCallback callback) {
+        int id = mBLASTSyncEngine.startSyncSet(this);
+        mTransactionCallbacksByPendingSyncId.put(id, callback);
+        return id;
+    }
+
+    void setSyncReady(int id) {
+        mBLASTSyncEngine.setReady(id);
+    }
+
+    @Override
+    public void transactionReady(int mSyncId, SurfaceControl.Transaction mergedTransaction) {
+        final IWindowContainerTransactionCallback callback =
+                mTransactionCallbacksByPendingSyncId.get(mSyncId);
+
+        try {
+            callback.transactionReady(mSyncId, mergedTransaction);
+        } catch (RemoteException e) {
+        }
+
+        mTransactionCallbacksByPendingSyncId.remove(mSyncId);
+    }
+
+    private void enforceStackPermission(String func) {
+        mService.mAmInternal.enforceCallingPermission(MANAGE_ACTIVITY_STACKS, func);
+    }
+}