Move PIP/MW mode callbacks to be on the client side

We now infer the PIP/MW mode change from the new configuration.
Note also that both
- Activity#isInPictureInPictureMode
- Activity#isInMultiWindowMode
infer the current state from the configuration rather than querying
against the WM.

Also in this CL:
- When in removePinnedStackInSurfaceTransaction, keep the pinned stack
hidden till the windowing mode is set to fullscreen, this is to surpress
the attempt to set the activities to be started in reparenting
- When in ActivityRecord#shouldBeVisible, should take account the force
hidden flag, which is not actually in use before

Bug: 144097203
Bug: 142282126
Bug: 138329093
Test: atest ActivityLifecyclePipTests \
            ActivityLifecycleSplitScreenTests \
            ActivityLifecycleTopResumedStateTests \
            PinnedStackTests \
            SplitScreenTests \
            ActivityTaskManagerServiceTests \
            RecentsAnimationTest \
            AssistantStackTests \
            StartActivityTests \
            ActivityVisibilityTests \
            MultiDisplaySecurityTests \
            MultiDisplaySystemDecorationTests
Change-Id: Ibe032b5e50ba5c6d6bc44ebb54d07ac974ebe656
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index e0ae750..17720a3 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -2748,11 +2748,7 @@
      * @return True if the activity is in multi-window mode.
      */
     public boolean isInMultiWindowMode() {
-        try {
-            return ActivityTaskManager.getService().isInMultiWindowMode(mToken);
-        } catch (RemoteException e) {
-        }
-        return false;
+        return mLastDispatchedIsInMultiWindowMode == Boolean.TRUE;
     }
 
     /**
@@ -2795,11 +2791,7 @@
      * @return True if the activity is in picture-in-picture mode.
      */
     public boolean isInPictureInPictureMode() {
-        try {
-            return ActivityTaskManager.getService().isInPictureInPictureMode(mToken);
-        } catch (RemoteException e) {
-        }
-        return false;
+        return mLastDispatchedIsInPictureInPictureMode == Boolean.TRUE;
     }
 
     /**
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 51eaff1..73566d9 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -17,6 +17,8 @@
 package android.app;
 
 import static android.app.ActivityManager.PROCESS_STATE_UNKNOWN;
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
 import static android.app.servertransaction.ActivityLifecycleItem.ON_CREATE;
 import static android.app.servertransaction.ActivityLifecycleItem.ON_DESTROY;
 import static android.app.servertransaction.ActivityLifecycleItem.ON_PAUSE;
@@ -407,6 +409,9 @@
     @GuardedBy("this")
     private @Nullable Map<SafeCancellationTransport, CancellationSignal> mRemoteCancellations;
 
+    private final Map<IBinder, Integer> mLastReportedWindowingMode = Collections.synchronizedMap(
+            new ArrayMap<>());
+
     private static final class ProviderKey {
         final String authority;
         final int userId;
@@ -3324,6 +3329,8 @@
                         " did not call through to super.onCreate()");
                 }
                 r.activity = activity;
+                mLastReportedWindowingMode.put(activity.getActivityToken(),
+                        config.windowConfiguration.getWindowingMode());
             }
             r.setState(ON_CREATE);
 
@@ -3747,32 +3754,6 @@
     }
 
     @Override
-    public void handleMultiWindowModeChanged(IBinder token, boolean isInMultiWindowMode,
-            Configuration overrideConfig) {
-        final ActivityClientRecord r = mActivities.get(token);
-        if (r != null) {
-            final Configuration newConfig = new Configuration(mConfiguration);
-            if (overrideConfig != null) {
-                newConfig.updateFrom(overrideConfig);
-            }
-            r.activity.dispatchMultiWindowModeChanged(isInMultiWindowMode, newConfig);
-        }
-    }
-
-    @Override
-    public void handlePictureInPictureModeChanged(IBinder token, boolean isInPipMode,
-            Configuration overrideConfig) {
-        final ActivityClientRecord r = mActivities.get(token);
-        if (r != null) {
-            final Configuration newConfig = new Configuration(mConfiguration);
-            if (overrideConfig != null) {
-                newConfig.updateFrom(overrideConfig);
-            }
-            r.activity.dispatchPictureInPictureModeChanged(isInPipMode, newConfig);
-        }
-    }
-
-    @Override
     public void handlePictureInPictureRequested(IBinder token) {
         final ActivityClientRecord r = mActivities.get(token);
         if (r == null) {
@@ -5269,8 +5250,15 @@
             throw e.rethrowFromSystemServer();
         }
 
+        // Save the current windowing mode to be restored and compared to the new configuration's
+        // windowing mode (needed because we update the last reported windowing mode when launching
+        // an activity and we can't tell inside performLaunchActivity whether we are relaunching)
+        final int oldWindowingMode = mLastReportedWindowingMode.getOrDefault(
+                r.activity.getActivityToken(), WINDOWING_MODE_UNDEFINED);
         handleRelaunchActivityInner(r, configChanges, tmp.pendingResults, tmp.pendingIntents,
                 pendingActions, tmp.startsNotResumed, tmp.overrideConfig, "handleRelaunchActivity");
+        mLastReportedWindowingMode.put(r.activity.getActivityToken(), oldWindowingMode);
+        handleWindowingModeChangeIfNeeded(r.activity, r.activity.mCurrentConfig);
 
         if (pendingActions != null) {
             // Only report a successful relaunch to WindowManager.
@@ -5553,6 +5541,10 @@
             throw new IllegalArgumentException("Activity token not set. Is the activity attached?");
         }
 
+        // multi-window / pip mode changes, if any, should be sent before the configuration change
+        // callback, see also PinnedStackTests#testConfigurationChangeOrderDuringTransition
+        handleWindowingModeChangeIfNeeded(activity, newConfig);
+
         boolean shouldChangeConfig = false;
         if (activity.mCurrentConfig == null) {
             shouldChangeConfig = true;
@@ -5747,6 +5739,35 @@
     }
 
     /**
+     * Sends windowing mode change callbacks to {@link Activity} if applicable.
+     *
+     * See also {@link Activity#onMultiWindowModeChanged(boolean, Configuration)} and
+     * {@link Activity#onPictureInPictureModeChanged(boolean, Configuration)}
+     */
+    private void handleWindowingModeChangeIfNeeded(Activity activity,
+            Configuration newConfiguration) {
+        final int newWindowingMode = newConfiguration.windowConfiguration.getWindowingMode();
+        final IBinder token = activity.getActivityToken();
+        final int oldWindowingMode = mLastReportedWindowingMode.getOrDefault(token,
+                WINDOWING_MODE_UNDEFINED);
+        if (oldWindowingMode == newWindowingMode) return;
+        // PiP callback is sent before the MW one.
+        if (newWindowingMode == WINDOWING_MODE_PINNED) {
+            activity.dispatchPictureInPictureModeChanged(true, newConfiguration);
+        } else if (oldWindowingMode == WINDOWING_MODE_PINNED) {
+            activity.dispatchPictureInPictureModeChanged(false, newConfiguration);
+        }
+        final boolean wasInMultiWindowMode = WindowConfiguration.inMultiWindowMode(
+                oldWindowingMode);
+        final boolean nowInMultiWindowMode = WindowConfiguration.inMultiWindowMode(
+                newWindowingMode);
+        if (wasInMultiWindowMode != nowInMultiWindowMode) {
+            activity.dispatchMultiWindowModeChanged(nowInMultiWindowMode, newConfiguration);
+        }
+        mLastReportedWindowingMode.put(token, newWindowingMode);
+    }
+
+    /**
      * Updates the application info.
      *
      * This only works in the system process. Must be called on the main thread.
diff --git a/core/java/android/app/ClientTransactionHandler.java b/core/java/android/app/ClientTransactionHandler.java
index d2235f1..83465b0 100644
--- a/core/java/android/app/ClientTransactionHandler.java
+++ b/core/java/android/app/ClientTransactionHandler.java
@@ -146,17 +146,9 @@
     /** Deliver result from another activity. */
     public abstract void handleSendResult(IBinder token, List<ResultInfo> results, String reason);
 
-    /** Deliver multi-window mode change notification. */
-    public abstract void handleMultiWindowModeChanged(IBinder token, boolean isInMultiWindowMode,
-            Configuration overrideConfig);
-
     /** Deliver new intent. */
     public abstract void handleNewIntent(IBinder token, List<ReferrerIntent> intents);
 
-    /** Deliver picture-in-picture mode change notification. */
-    public abstract void handlePictureInPictureModeChanged(IBinder token, boolean isInPipMode,
-            Configuration overrideConfig);
-
     /** Request that an activity enter picture-in-picture. */
     public abstract void handlePictureInPictureRequested(IBinder token);
 
diff --git a/core/java/android/app/IActivityTaskManager.aidl b/core/java/android/app/IActivityTaskManager.aidl
index b7ceb6a..3ce7689 100644
--- a/core/java/android/app/IActivityTaskManager.aidl
+++ b/core/java/android/app/IActivityTaskManager.aidl
@@ -300,8 +300,6 @@
 
     void suppressResizeConfigChanges(boolean suppress);
     boolean moveTopActivityToPinnedStack(int stackId, in Rect bounds);
-    boolean isInMultiWindowMode(in IBinder token);
-    boolean isInPictureInPictureMode(in IBinder token);
     boolean enterPictureInPictureMode(in IBinder token, in PictureInPictureParams params);
     void setPictureInPictureParams(in IBinder token, in PictureInPictureParams params);
     void requestPictureInPictureMode(in IBinder token);
diff --git a/core/java/android/app/WindowConfiguration.java b/core/java/android/app/WindowConfiguration.java
index 37e07de..a486b95 100644
--- a/core/java/android/app/WindowConfiguration.java
+++ b/core/java/android/app/WindowConfiguration.java
@@ -727,6 +727,16 @@
     }
 
     /**
+     * Returns {@code true} if the windowingMode represents a window in multi-window mode.
+     * I.e. sharing the screen with another activity.
+     * @hide
+     */
+    public static boolean inMultiWindowMode(int windowingMode) {
+        return windowingMode != WINDOWING_MODE_FULLSCREEN
+                && windowingMode != WINDOWING_MODE_UNDEFINED;
+    }
+
+    /**
      * Returns true if the windowingMode represents a split window.
      * @hide
      */
diff --git a/core/java/android/app/servertransaction/MultiWindowModeChangeItem.java b/core/java/android/app/servertransaction/MultiWindowModeChangeItem.java
deleted file mode 100644
index b150717..0000000
--- a/core/java/android/app/servertransaction/MultiWindowModeChangeItem.java
+++ /dev/null
@@ -1,121 +0,0 @@
-/*
- * Copyright 2017 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 android.app.servertransaction;
-
-import android.app.ClientTransactionHandler;
-import android.content.res.Configuration;
-import android.os.IBinder;
-import android.os.Parcel;
-
-import java.util.Objects;
-
-/**
- * Multi-window mode change message.
- * @hide
- */
-// TODO(lifecycler): Remove the use of this and just use the configuration change message to
-// communicate multi-window mode change with WindowConfiguration.
-public class MultiWindowModeChangeItem extends ClientTransactionItem {
-
-    private boolean mIsInMultiWindowMode;
-    private Configuration mOverrideConfig;
-
-    @Override
-    public void execute(ClientTransactionHandler client, IBinder token,
-            PendingTransactionActions pendingActions) {
-        client.handleMultiWindowModeChanged(token, mIsInMultiWindowMode, mOverrideConfig);
-    }
-
-
-    // ObjectPoolItem implementation
-
-    private MultiWindowModeChangeItem() {}
-
-    /** Obtain an instance initialized with provided params. */
-    public static MultiWindowModeChangeItem obtain(boolean isInMultiWindowMode,
-            Configuration overrideConfig) {
-        MultiWindowModeChangeItem instance = ObjectPool.obtain(MultiWindowModeChangeItem.class);
-        if (instance == null) {
-            instance = new MultiWindowModeChangeItem();
-        }
-        instance.mIsInMultiWindowMode = isInMultiWindowMode;
-        instance.mOverrideConfig = overrideConfig;
-
-        return instance;
-    }
-
-    @Override
-    public void recycle() {
-        mIsInMultiWindowMode = false;
-        mOverrideConfig = null;
-        ObjectPool.recycle(this);
-    }
-
-
-    // Parcelable implementation
-
-    /** Write to Parcel. */
-    @Override
-    public void writeToParcel(Parcel dest, int flags) {
-        dest.writeBoolean(mIsInMultiWindowMode);
-        dest.writeTypedObject(mOverrideConfig, flags);
-    }
-
-    /** Read from Parcel. */
-    private MultiWindowModeChangeItem(Parcel in) {
-        mIsInMultiWindowMode = in.readBoolean();
-        mOverrideConfig = in.readTypedObject(Configuration.CREATOR);
-    }
-
-    public static final @android.annotation.NonNull Creator<MultiWindowModeChangeItem> CREATOR =
-            new Creator<MultiWindowModeChangeItem>() {
-        public MultiWindowModeChangeItem createFromParcel(Parcel in) {
-            return new MultiWindowModeChangeItem(in);
-        }
-
-        public MultiWindowModeChangeItem[] newArray(int size) {
-            return new MultiWindowModeChangeItem[size];
-        }
-    };
-
-    @Override
-    public boolean equals(Object o) {
-        if (this == o) {
-            return true;
-        }
-        if (o == null || getClass() != o.getClass()) {
-            return false;
-        }
-        final MultiWindowModeChangeItem other = (MultiWindowModeChangeItem) o;
-        return mIsInMultiWindowMode == other.mIsInMultiWindowMode
-                && Objects.equals(mOverrideConfig, other.mOverrideConfig);
-    }
-
-    @Override
-    public int hashCode() {
-        int result = 17;
-        result = 31 * result + (mIsInMultiWindowMode ? 1 : 0);
-        result = 31 * result + mOverrideConfig.hashCode();
-        return result;
-    }
-
-    @Override
-    public String toString() {
-        return "MultiWindowModeChangeItem{isInMultiWindowMode=" + mIsInMultiWindowMode
-                + ",overrideConfig=" + mOverrideConfig + "}";
-    }
-}
diff --git a/core/java/android/app/servertransaction/PipModeChangeItem.java b/core/java/android/app/servertransaction/PipModeChangeItem.java
deleted file mode 100644
index 1955897..0000000
--- a/core/java/android/app/servertransaction/PipModeChangeItem.java
+++ /dev/null
@@ -1,119 +0,0 @@
-/*
- * Copyright 2017 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 android.app.servertransaction;
-
-import android.app.ClientTransactionHandler;
-import android.content.res.Configuration;
-import android.os.IBinder;
-import android.os.Parcel;
-
-import java.util.Objects;
-
-/**
- * Picture in picture mode change message.
- * @hide
- */
-// TODO(lifecycler): Remove the use of this and just use the configuration change message to
-// communicate multi-window mode change with WindowConfiguration.
-public class PipModeChangeItem extends ClientTransactionItem {
-
-    private boolean mIsInPipMode;
-    private Configuration mOverrideConfig;
-
-    @Override
-    public void execute(ClientTransactionHandler client, IBinder token,
-            PendingTransactionActions pendingActions) {
-        client.handlePictureInPictureModeChanged(token, mIsInPipMode, mOverrideConfig);
-    }
-
-
-    // ObjectPoolItem implementation
-
-    private PipModeChangeItem() {}
-
-    /** Obtain an instance initialized with provided params. */
-    public static PipModeChangeItem obtain(boolean isInPipMode, Configuration overrideConfig) {
-        PipModeChangeItem instance = ObjectPool.obtain(PipModeChangeItem.class);
-        if (instance == null) {
-            instance = new PipModeChangeItem();
-        }
-        instance.mIsInPipMode = isInPipMode;
-        instance.mOverrideConfig = overrideConfig;
-
-        return instance;
-    }
-
-    @Override
-    public void recycle() {
-        mIsInPipMode = false;
-        mOverrideConfig = null;
-        ObjectPool.recycle(this);
-    }
-
-
-    // Parcelable implementation
-
-    /** Write to Parcel. */
-    public void writeToParcel(Parcel dest, int flags) {
-        dest.writeBoolean(mIsInPipMode);
-        dest.writeTypedObject(mOverrideConfig, flags);
-    }
-
-    /** Read from Parcel. */
-    private PipModeChangeItem(Parcel in) {
-        mIsInPipMode = in.readBoolean();
-        mOverrideConfig = in.readTypedObject(Configuration.CREATOR);
-    }
-
-    public static final @android.annotation.NonNull Creator<PipModeChangeItem> CREATOR =
-            new Creator<PipModeChangeItem>() {
-        public PipModeChangeItem createFromParcel(Parcel in) {
-            return new PipModeChangeItem(in);
-        }
-
-        public PipModeChangeItem[] newArray(int size) {
-            return new PipModeChangeItem[size];
-        }
-    };
-
-    @Override
-    public boolean equals(Object o) {
-        if (this == o) {
-            return true;
-        }
-        if (o == null || getClass() != o.getClass()) {
-            return false;
-        }
-        final PipModeChangeItem other = (PipModeChangeItem) o;
-        return mIsInPipMode == other.mIsInPipMode
-                && Objects.equals(mOverrideConfig, other.mOverrideConfig);
-    }
-
-    @Override
-    public int hashCode() {
-        int result = 17;
-        result = 31 * result + (mIsInPipMode ? 1 : 0);
-        result = 31 * result + mOverrideConfig.hashCode();
-        return result;
-    }
-
-    @Override
-    public String toString() {
-        return "PipModeChangeItem{isInPipMode=" + mIsInPipMode
-                + ",overrideConfig=" + mOverrideConfig + "}";
-    }
-}
diff --git a/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java b/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java
index 4b29d59..107fe3f 100644
--- a/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java
+++ b/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java
@@ -198,21 +198,6 @@
     }
 
     @Test
-    public void testRecycleMultiWindowModeChangeItem() {
-        MultiWindowModeChangeItem emptyItem = MultiWindowModeChangeItem.obtain(false, null);
-        MultiWindowModeChangeItem item = MultiWindowModeChangeItem.obtain(true, config());
-        assertNotSame(item, emptyItem);
-        assertFalse(item.equals(emptyItem));
-
-        item.recycle();
-        assertEquals(item, emptyItem);
-
-        MultiWindowModeChangeItem item2 = MultiWindowModeChangeItem.obtain(true, config());
-        assertSame(item, item2);
-        assertFalse(item2.equals(emptyItem));
-    }
-
-    @Test
     public void testRecycleNewIntentItem() {
         NewIntentItem emptyItem = NewIntentItem.obtain(null, false);
         NewIntentItem item = NewIntentItem.obtain(referrerIntentList(), false);
@@ -243,21 +228,6 @@
     }
 
     @Test
-    public void testRecyclePipModeChangeItem() {
-        PipModeChangeItem emptyItem = PipModeChangeItem.obtain(false, null);
-        PipModeChangeItem item = PipModeChangeItem.obtain(true, config());
-        assertNotSame(item, emptyItem);
-        assertFalse(item.equals(emptyItem));
-
-        item.recycle();
-        assertEquals(item, emptyItem);
-
-        PipModeChangeItem item2 = PipModeChangeItem.obtain(true, config());
-        assertSame(item, item2);
-        assertFalse(item2.equals(emptyItem));
-    }
-
-    @Test
     public void testRecycleResumeActivityItem() {
         ResumeActivityItem emptyItem = ResumeActivityItem.obtain(false);
         ResumeActivityItem item = ResumeActivityItem.obtain(3, true);
diff --git a/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java b/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
index 3766cf7..47f9323 100644
--- a/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
+++ b/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
@@ -153,34 +153,6 @@
     }
 
     @Test
-    public void testPipModeChange() {
-        // Write to parcel
-        PipModeChangeItem item = PipModeChangeItem.obtain(true /* isInPipMode */, config());
-        writeAndPrepareForReading(item);
-
-        // Read from parcel and assert
-        PipModeChangeItem result = PipModeChangeItem.CREATOR.createFromParcel(mParcel);
-
-        assertEquals(item.hashCode(), result.hashCode());
-        assertTrue(item.equals(result));
-    }
-
-    @Test
-    public void testMultiWindowModeChange() {
-        // Write to parcel
-        MultiWindowModeChangeItem item = MultiWindowModeChangeItem.obtain(
-                true /* isInMultiWindowMode */, config());
-        writeAndPrepareForReading(item);
-
-        // Read from parcel and assert
-        MultiWindowModeChangeItem result =
-                MultiWindowModeChangeItem.CREATOR.createFromParcel(mParcel);
-
-        assertEquals(item.hashCode(), result.hashCode());
-        assertTrue(item.equals(result));
-    }
-
-    @Test
     public void testDestroy() {
         DestroyActivityItem item = DestroyActivityItem.obtain(true /* finished */,
                 135 /* configChanges */);
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index dcdbfded..7bbe5cc 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -235,10 +235,8 @@
 import android.app.servertransaction.ClientTransactionItem;
 import android.app.servertransaction.DestroyActivityItem;
 import android.app.servertransaction.MoveToDisplayItem;
-import android.app.servertransaction.MultiWindowModeChangeItem;
 import android.app.servertransaction.NewIntentItem;
 import android.app.servertransaction.PauseActivityItem;
-import android.app.servertransaction.PipModeChangeItem;
 import android.app.servertransaction.ResumeActivityItem;
 import android.app.servertransaction.StartActivityItem;
 import android.app.servertransaction.StopActivityItem;
@@ -1155,11 +1153,6 @@
             return;
         }
 
-        if (task.getStack().deferScheduleMultiWindowModeChanged()) {
-            // Don't do anything if we are currently deferring multi-window mode change.
-            return;
-        }
-
         // An activity is considered to be in multi-window mode if its task isn't fullscreen.
         final boolean inMultiWindowMode = inMultiWindowMode();
         if (inMultiWindowMode != mLastReportedMultiWindowMode) {
@@ -1167,20 +1160,13 @@
                 updatePictureInPictureMode(null, false);
             } else {
                 mLastReportedMultiWindowMode = inMultiWindowMode;
-                scheduleMultiWindowModeChanged(getConfiguration());
+                computeConfigurationAfterMultiWindowModeChange();
+                ensureActivityConfiguration(0 /* globalChanges */, PRESERVE_WINDOWS,
+                        true /* ignoreVisibility */);
             }
         }
     }
 
-    private void scheduleMultiWindowModeChanged(Configuration overrideConfig) {
-        try {
-            mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(), appToken,
-                    MultiWindowModeChangeItem.obtain(mLastReportedMultiWindowMode, overrideConfig));
-        } catch (Exception e) {
-            // If process died, I don't care.
-        }
-    }
-
     void updatePictureInPictureMode(Rect targetStackBounds, boolean forceUpdate) {
         if (task == null || task.getStack() == null || !attachedToProcess()) {
             return;
@@ -1188,39 +1174,26 @@
 
         final boolean inPictureInPictureMode = inPinnedWindowingMode() && targetStackBounds != null;
         if (inPictureInPictureMode != mLastReportedPictureInPictureMode || forceUpdate) {
-            // Picture-in-picture mode change normal triggers also multi-window mode change
-            // except transitions between pip and split screen mode, so update that here in order.
-            // Set the last reported MW state to the same as the PiP state since we haven't yet
-            // actually resized the task (these callbacks need to proceed the configuration change
-            // from the resize).
-            // TODO(110009072): Once we move these callbacks to the client, remove all logic related
-            // to forcing the update of the picture-in-picture mode as a part of the PiP animation.
-            final boolean shouldScheduleMultiWindowModeChange =
-                    mLastReportedMultiWindowMode != inMultiWindowMode();
+            // Picture-in-picture mode changes also trigger a multi-window mode change as well, so
+            // update that here in order. Set the last reported MW state to the same as the PiP
+            // state since we haven't yet actually resized the task (these callbacks need to
+            // precede the configuration change from the resize.
             mLastReportedPictureInPictureMode = inPictureInPictureMode;
             mLastReportedMultiWindowMode = inPictureInPictureMode;
-            final Configuration newConfig = new Configuration();
             if (targetStackBounds != null && !targetStackBounds.isEmpty()) {
-                newConfig.setTo(task.getRequestedOverrideConfiguration());
-                Rect outBounds = newConfig.windowConfiguration.getBounds();
-                task.adjustForMinimalTaskDimensions(outBounds, outBounds);
-                task.computeConfigResourceOverrides(newConfig, task.getParent().getConfiguration());
+                computeConfigurationAfterMultiWindowModeChange();
             }
-            schedulePictureInPictureModeChanged(newConfig);
-            if (shouldScheduleMultiWindowModeChange) {
-                scheduleMultiWindowModeChanged(newConfig);
-            }
+            ensureActivityConfiguration(0 /* globalChanges */, PRESERVE_WINDOWS,
+                    true /* ignoreVisibility */);
         }
     }
 
-    private void schedulePictureInPictureModeChanged(Configuration overrideConfig) {
-        try {
-            mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(), appToken,
-                    PipModeChangeItem.obtain(mLastReportedPictureInPictureMode,
-                            overrideConfig));
-        } catch (Exception e) {
-            // If process died, no one cares.
-        }
+    private void computeConfigurationAfterMultiWindowModeChange() {
+        final Configuration newConfig = new Configuration();
+        newConfig.setTo(task.getRequestedOverrideConfiguration());
+        Rect outBounds = newConfig.windowConfiguration.getBounds();
+        task.adjustForMinimalTaskDimensions(outBounds, outBounds);
+        task.computeConfigResourceOverrides(newConfig, task.getParent().getConfiguration());
     }
 
     Task getTask() {
@@ -4531,6 +4504,15 @@
             return false;
         }
 
+        // Activity in a pinned stack should not be visible if the stack is in force hidden state.
+        // Typically due to the FLAG_FORCE_HIDDEN_FOR_PINNED_TASK set on the stack, which is a
+        // work around to send onStop before windowing mode change callbacks.
+        // See also ActivityStackSupervisor#removePinnedStackInSurfaceTransaction
+        // TODO: Should we ever be visible if the stack/task is invisible?
+        if (inPinnedWindowingMode() && stack.isForceHidden()) {
+            return false;
+        }
+
         // Check if the activity is on a sleeping display, and if it can turn it ON.
         if (getDisplay().isSleeping()) {
             final boolean canTurnScreenOn = !mSetToSleep || canTurnScreenOn()
diff --git a/services/core/java/com/android/server/wm/ActivityStack.java b/services/core/java/com/android/server/wm/ActivityStack.java
index 2ab03ce..46b7b28 100644
--- a/services/core/java/com/android/server/wm/ActivityStack.java
+++ b/services/core/java/com/android/server/wm/ActivityStack.java
@@ -3344,23 +3344,6 @@
         getDisplayContent().getPinnedStackController().setActions(actions);
     }
 
-    /**
-     * @return True if we are currently animating the pinned stack from fullscreen to non-fullscreen
-     *         bounds and we have a deferred PiP mode changed callback set with the animation.
-     */
-    public boolean deferScheduleMultiWindowModeChanged() {
-        if (inPinnedWindowingMode()) {
-            // For the pinned stack, the deferring of the multi-window mode changed is tied to the
-            // transition animation into picture-in-picture, and is called once the animation
-            // completes, or is interrupted in a way that would leave the stack in a non-fullscreen
-            // state.
-            // @see BoundsAnimationController
-            // @see BoundsAnimationControllerTests
-            return (mBoundsAnimatingRequested || mBoundsAnimating);
-        }
-        return false;
-    }
-
     public boolean isForceScaled() {
         return mBoundsAnimating;
     }
diff --git a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
index fe9e5f3c..6f0a339 100644
--- a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
@@ -1452,16 +1452,15 @@
         /**
          * Workaround: Force-stop all the activities in the pinned stack before we reparent them
          * to the fullscreen stack.  This is to guarantee that when we are removing a stack,
-         * that the client receives onStop() before it is reparented.  We do this by detaching
-         * the stack from the display so that it will be considered invisible when
-         * ensureActivitiesVisible() is called, and all of its activities will be marked
-         * invisible as well and added to the stopping list.  After which we process the
+         * that the client receives onStop() before new windowing mode is set.
+         * We do this by detaching the stack from the display so that it will be considered
+         * invisible when ensureActivitiesVisible() is called, and all of its activities will be
+         * marked invisible as well and added to the stopping list.  After which we process the
          * stopping list by handling the idle.
          */
         stack.cancelAnimation();
         stack.setForceHidden(FLAG_FORCE_HIDDEN_FOR_PINNED_TASK, true /* set */);
         stack.ensureActivitiesVisible(null, 0, PRESERVE_WINDOWS);
-        stack.setForceHidden(FLAG_FORCE_HIDDEN_FOR_PINNED_TASK, false /* set */);
         activityIdleInternal(null /* idleActivity */, false /* fromTimeout */,
                 true /* processPausingActivities */, null /* configuration */);
 
@@ -1478,6 +1477,9 @@
                 toDisplay.getDefaultTaskDisplayArea().positionStackAtBottom(stack);
             }
 
+            // Follow on the workaround: activities are kept force hidden till the new windowing
+            // mode is set.
+            stack.setForceHidden(FLAG_FORCE_HIDDEN_FOR_PINNED_TASK, false /* set */);
             mRootWindowContainer.ensureActivitiesVisible(null, 0, PRESERVE_WINDOWS);
             mRootWindowContainer.resumeFocusedStacksTopActivities();
         } finally {
@@ -2242,12 +2244,6 @@
     }
 
     void scheduleUpdateMultiWindowMode(Task task) {
-        // If the stack is animating in a way where we will be forcing a multi-mode change at the
-        // end, then ensure that we defer all in between multi-window mode changes
-        if (task.getStack().deferScheduleMultiWindowModeChanged()) {
-            return;
-        }
-
         final PooledConsumer c = PooledLambda.obtainConsumer(
                 ActivityStackSupervisor::addToMultiWindowModeChangedList, this,
                 PooledLambda.__(ActivityRecord.class));
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 5220fb2..511baa5 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -4036,36 +4036,8 @@
         }
     }
 
-    @Override
-    public boolean isInMultiWindowMode(IBinder token) {
-        final long origId = Binder.clearCallingIdentity();
-        try {
-            synchronized (mGlobalLock) {
-                final ActivityRecord r = ActivityRecord.isInStackLocked(token);
-                if (r == null) {
-                    return false;
-                }
-                // An activity is consider to be in multi-window mode if its task isn't fullscreen.
-                return r.inMultiWindowMode();
-            }
-        } finally {
-            Binder.restoreCallingIdentity(origId);
-        }
-    }
-
-    @Override
-    public boolean isInPictureInPictureMode(IBinder token) {
-        final long origId = Binder.clearCallingIdentity();
-        try {
-            synchronized (mGlobalLock) {
-                return isInPictureInPictureMode(ActivityRecord.forTokenLocked(token));
-            }
-        } finally {
-            Binder.restoreCallingIdentity(origId);
-        }
-    }
-
-    private boolean isInPictureInPictureMode(ActivityRecord r) {
+    @VisibleForTesting
+    boolean isInPictureInPictureMode(ActivityRecord r) {
         return r != null
                 && r.getRootTask() != null
                 && r.inPinnedWindowingMode()
diff --git a/services/core/java/com/android/server/wm/ConfigurationContainer.java b/services/core/java/com/android/server/wm/ConfigurationContainer.java
index 1036af6..51357d1 100644
--- a/services/core/java/com/android/server/wm/ConfigurationContainer.java
+++ b/services/core/java/com/android/server/wm/ConfigurationContainer.java
@@ -23,11 +23,9 @@
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
-import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
 import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
 import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
-import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
 import static android.app.WindowConfiguration.activityTypeToString;
 import static android.app.WindowConfiguration.windowingModeToString;
 
@@ -391,8 +389,7 @@
     public boolean inMultiWindowMode() {
         /*@WindowConfiguration.WindowingMode*/ int windowingMode =
                 mFullConfiguration.windowConfiguration.getWindowingMode();
-        return windowingMode != WINDOWING_MODE_FULLSCREEN
-                && windowingMode != WINDOWING_MODE_UNDEFINED;
+        return WindowConfiguration.inMultiWindowMode(windowingMode);
     }
 
     /** Returns true if this container is currently in split-screen windowing mode. */