Merge "fix(window magnification): call notifySourceBoundsChanged when the animation ends" into main
diff --git a/core/java/android/window/WindowOnBackInvokedDispatcher.java b/core/java/android/window/WindowOnBackInvokedDispatcher.java
index bcbac93..47a4052 100644
--- a/core/java/android/window/WindowOnBackInvokedDispatcher.java
+++ b/core/java/android/window/WindowOnBackInvokedDispatcher.java
@@ -36,6 +36,8 @@
 import androidx.annotation.VisibleForTesting;
 
 
+import com.android.internal.annotations.GuardedBy;
+
 import java.io.PrintWriter;
 import java.lang.ref.WeakReference;
 import java.util.ArrayList;
@@ -75,14 +77,17 @@
     @Nullable
     private ImeBackAnimationController mImeBackAnimationController;
 
+    @GuardedBy("mLock")
     /** Convenience hashmap to quickly decide if a callback has been added. */
     private final HashMap<OnBackInvokedCallback, Integer> mAllCallbacks = new HashMap<>();
     /** Holds all callbacks by priorities. */
 
     @VisibleForTesting
+    @GuardedBy("mLock")
     public final TreeMap<Integer, ArrayList<OnBackInvokedCallback>>
             mOnBackInvokedCallbacks = new TreeMap<>();
     private Checker mChecker;
+    private final Object mLock = new Object();
 
     public WindowOnBackInvokedDispatcher(@NonNull Context context) {
         mChecker = new Checker(context);
@@ -94,20 +99,24 @@
      */
     public void attachToWindow(@NonNull IWindowSession windowSession, @NonNull IWindow window,
             @Nullable ImeBackAnimationController imeBackAnimationController) {
-        mWindowSession = windowSession;
-        mWindow = window;
-        mImeBackAnimationController = imeBackAnimationController;
-        if (!mAllCallbacks.isEmpty()) {
-            setTopOnBackInvokedCallback(getTopCallback());
+        synchronized (mLock) {
+            mWindowSession = windowSession;
+            mWindow = window;
+            mImeBackAnimationController = imeBackAnimationController;
+            if (!mAllCallbacks.isEmpty()) {
+                setTopOnBackInvokedCallback(getTopCallback());
+            }
         }
     }
 
     /** Detaches the dispatcher instance from its window. */
     public void detachFromWindow() {
-        clear();
-        mWindow = null;
-        mWindowSession = null;
-        mImeBackAnimationController = null;
+        synchronized (mLock) {
+            clear();
+            mWindow = null;
+            mWindowSession = null;
+            mImeBackAnimationController = null;
+        }
     }
 
     // TODO: Take an Executor for the callback to run on.
@@ -125,65 +134,71 @@
      */
     public void registerOnBackInvokedCallbackUnchecked(
             @NonNull OnBackInvokedCallback callback, @Priority int priority) {
-        if (mImeDispatcher != null) {
-            mImeDispatcher.registerOnBackInvokedCallback(priority, callback);
-            return;
-        }
-        if (!mOnBackInvokedCallbacks.containsKey(priority)) {
-            mOnBackInvokedCallbacks.put(priority, new ArrayList<>());
-        }
-        if (callback instanceof ImeOnBackInvokedDispatcher.DefaultImeOnBackAnimationCallback) {
-            callback = mImeBackAnimationController;
-        }
-        ArrayList<OnBackInvokedCallback> callbacks = mOnBackInvokedCallbacks.get(priority);
-
-        // If callback has already been added, remove it and re-add it.
-        if (mAllCallbacks.containsKey(callback)) {
-            if (DEBUG) {
-                Log.i(TAG, "Callback already added. Removing and re-adding it.");
+        synchronized (mLock) {
+            if (mImeDispatcher != null) {
+                mImeDispatcher.registerOnBackInvokedCallback(priority, callback);
+                return;
             }
-            Integer prevPriority = mAllCallbacks.get(callback);
-            mOnBackInvokedCallbacks.get(prevPriority).remove(callback);
-        }
+            if (!mOnBackInvokedCallbacks.containsKey(priority)) {
+                mOnBackInvokedCallbacks.put(priority, new ArrayList<>());
+            }
+            if (callback instanceof ImeOnBackInvokedDispatcher.DefaultImeOnBackAnimationCallback) {
+                callback = mImeBackAnimationController;
+            }
+            ArrayList<OnBackInvokedCallback> callbacks = mOnBackInvokedCallbacks.get(priority);
 
-        OnBackInvokedCallback previousTopCallback = getTopCallback();
-        callbacks.add(callback);
-        mAllCallbacks.put(callback, priority);
-        if (previousTopCallback == null
-                || (previousTopCallback != callback
-                        && mAllCallbacks.get(previousTopCallback) <= priority)) {
-            setTopOnBackInvokedCallback(callback);
+            // If callback has already been added, remove it and re-add it.
+            if (mAllCallbacks.containsKey(callback)) {
+                if (DEBUG) {
+                    Log.i(TAG, "Callback already added. Removing and re-adding it.");
+                }
+                Integer prevPriority = mAllCallbacks.get(callback);
+                mOnBackInvokedCallbacks.get(prevPriority).remove(callback);
+            }
+
+            OnBackInvokedCallback previousTopCallback = getTopCallback();
+            callbacks.add(callback);
+            mAllCallbacks.put(callback, priority);
+            if (previousTopCallback == null
+                    || (previousTopCallback != callback
+                    && mAllCallbacks.get(previousTopCallback) <= priority)) {
+                setTopOnBackInvokedCallback(callback);
+            }
         }
     }
 
     @Override
     public void unregisterOnBackInvokedCallback(@NonNull OnBackInvokedCallback callback) {
-        if (mImeDispatcher != null) {
-            mImeDispatcher.unregisterOnBackInvokedCallback(callback);
-            return;
-        }
-        if (callback instanceof ImeOnBackInvokedDispatcher.DefaultImeOnBackAnimationCallback) {
-            callback = mImeBackAnimationController;
-        }
-        if (!mAllCallbacks.containsKey(callback)) {
-            if (DEBUG) {
-                Log.i(TAG, "Callback not found. returning...");
+        synchronized (mLock) {
+            if (mImeDispatcher != null) {
+                mImeDispatcher.unregisterOnBackInvokedCallback(callback);
+                return;
             }
-            return;
-        }
-        OnBackInvokedCallback previousTopCallback = getTopCallback();
-        Integer priority = mAllCallbacks.get(callback);
-        ArrayList<OnBackInvokedCallback> callbacks = mOnBackInvokedCallbacks.get(priority);
-        callbacks.remove(callback);
-        if (callbacks.isEmpty()) {
-            mOnBackInvokedCallbacks.remove(priority);
-        }
-        mAllCallbacks.remove(callback);
-        // Re-populate the top callback to WM if the removed callback was previously the top one.
-        if (previousTopCallback == callback) {
-            // We should call onBackCancelled() when an active callback is removed from dispatcher.
-            sendCancelledIfInProgress(callback);
-            setTopOnBackInvokedCallback(getTopCallback());
+            if (callback instanceof ImeOnBackInvokedDispatcher.DefaultImeOnBackAnimationCallback) {
+                callback = mImeBackAnimationController;
+            }
+            if (!mAllCallbacks.containsKey(callback)) {
+                if (DEBUG) {
+                    Log.i(TAG, "Callback not found. returning...");
+                }
+                return;
+            }
+            OnBackInvokedCallback previousTopCallback = getTopCallback();
+            Integer priority = mAllCallbacks.get(callback);
+            ArrayList<OnBackInvokedCallback> callbacks = mOnBackInvokedCallbacks.get(priority);
+            callbacks.remove(callback);
+            if (callbacks.isEmpty()) {
+                mOnBackInvokedCallbacks.remove(priority);
+            }
+            mAllCallbacks.remove(callback);
+            // Re-populate the top callback to WM if the removed callback was previously the top
+            // one.
+            if (previousTopCallback == callback) {
+                // We should call onBackCancelled() when an active callback is removed from
+                // dispatcher.
+                sendCancelledIfInProgress(callback);
+                setTopOnBackInvokedCallback(getTopCallback());
+            }
         }
     }
 
@@ -191,15 +206,21 @@
      * Indicates if the dispatcher is actively dispatching to a callback.
      */
     public boolean isDispatching() {
-        return mIsDispatching;
+        synchronized (mLock) {
+            return mIsDispatching;
+        }
     }
 
     private void onStartDispatching() {
-        mIsDispatching = true;
+        synchronized (mLock) {
+            mIsDispatching = true;
+        }
     }
 
     private void onStopDispatching() {
-        mIsDispatching = false;
+        synchronized (mLock) {
+            mIsDispatching = false;
+        }
     }
 
     private void sendCancelledIfInProgress(@NonNull OnBackInvokedCallback callback) {
@@ -223,27 +244,29 @@
 
     /** Clears all registered callbacks on the instance. */
     public void clear() {
-        if (mImeDispatcher != null) {
-            mImeDispatcher.clear();
-            mImeDispatcher = null;
-        }
-        if (!mAllCallbacks.isEmpty()) {
-            OnBackInvokedCallback topCallback = getTopCallback();
-            if (topCallback != null) {
-                sendCancelledIfInProgress(topCallback);
-            } else {
-                // Should not be possible
-                Log.e(TAG, "There is no topCallback, even if mAllCallbacks is not empty");
+        synchronized (mLock) {
+            if (mImeDispatcher != null) {
+                mImeDispatcher.clear();
+                mImeDispatcher = null;
             }
-            // Clear binder references in WM.
-            setTopOnBackInvokedCallback(null);
-        }
+            if (!mAllCallbacks.isEmpty()) {
+                OnBackInvokedCallback topCallback = getTopCallback();
+                if (topCallback != null) {
+                    sendCancelledIfInProgress(topCallback);
+                } else {
+                    // Should not be possible
+                    Log.e(TAG, "There is no topCallback, even if mAllCallbacks is not empty");
+                }
+                // Clear binder references in WM.
+                setTopOnBackInvokedCallback(null);
+            }
 
-        // We should also stop running animations since all callbacks have been removed.
-        // note: mSpring.skipToEnd(), in ProgressAnimator.reset(), requires the main handler.
-        Handler.getMain().post(mProgressAnimator::reset);
-        mAllCallbacks.clear();
-        mOnBackInvokedCallbacks.clear();
+            // We should also stop running animations since all callbacks have been removed.
+            // note: mSpring.skipToEnd(), in ProgressAnimator.reset(), requires the main handler.
+            Handler.getMain().post(mProgressAnimator::reset);
+            mAllCallbacks.clear();
+            mOnBackInvokedCallbacks.clear();
+        }
     }
 
     private void setTopOnBackInvokedCallback(@Nullable OnBackInvokedCallback callback) {
@@ -275,13 +298,15 @@
     }
 
     public OnBackInvokedCallback getTopCallback() {
-        if (mAllCallbacks.isEmpty()) {
-            return null;
-        }
-        for (Integer priority : mOnBackInvokedCallbacks.descendingKeySet()) {
-            ArrayList<OnBackInvokedCallback> callbacks = mOnBackInvokedCallbacks.get(priority);
-            if (!callbacks.isEmpty()) {
-                return callbacks.get(callbacks.size() - 1);
+        synchronized (mLock) {
+            if (mAllCallbacks.isEmpty()) {
+                return null;
+            }
+            for (Integer priority : mOnBackInvokedCallbacks.descendingKeySet()) {
+                ArrayList<OnBackInvokedCallback> callbacks = mOnBackInvokedCallbacks.get(priority);
+                if (!callbacks.isEmpty()) {
+                    return callbacks.get(callbacks.size() - 1);
+                }
             }
         }
         return null;
@@ -315,16 +340,18 @@
     public void dump(String prefix, PrintWriter writer) {
         String innerPrefix = prefix + "    ";
         writer.println(prefix + "WindowOnBackDispatcher:");
-        if (mAllCallbacks.isEmpty()) {
-            writer.println(prefix + "<None>");
-            return;
-        }
+        synchronized (mLock) {
+            if (mAllCallbacks.isEmpty()) {
+                writer.println(prefix + "<None>");
+                return;
+            }
 
-        writer.println(innerPrefix + "Top Callback: " + getTopCallback());
-        writer.println(innerPrefix + "Callbacks: ");
-        mAllCallbacks.forEach((callback, priority) -> {
-            writer.println(innerPrefix + "  Callback: " + callback + " Priority=" + priority);
-        });
+            writer.println(innerPrefix + "Top Callback: " + getTopCallback());
+            writer.println(innerPrefix + "Callbacks: ");
+            mAllCallbacks.forEach((callback, priority) -> {
+                writer.println(innerPrefix + "  Callback: " + callback + " Priority=" + priority);
+            });
+        }
     }
 
     static class OnBackInvokedCallbackWrapper extends IOnBackInvokedCallback.Stub {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationRunner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationRunner.java
index a32b435..4988a94 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationRunner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationRunner.java
@@ -28,6 +28,7 @@
 import android.window.IBackAnimationRunner;
 import android.window.IOnBackInvokedCallback;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.jank.Cuj.CujType;
 import com.android.wm.shell.common.InteractionJankMonitorUtils;
 
@@ -108,7 +109,8 @@
         }
     }
 
-    private boolean shouldMonitorCUJ(RemoteAnimationTarget[] apps) {
+    @VisibleForTesting
+    boolean shouldMonitorCUJ(RemoteAnimationTarget[] apps) {
         return apps.length > 0 && mCujType != NO_CUJ;
     }
 
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
index 4061763..65169e3 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
@@ -596,6 +596,7 @@
 
         // Set up the monitoring objects.
         doNothing().when(runner).onAnimationStart(anyInt(), any(), any(), any(), any());
+        doReturn(false).when(animationRunner).shouldMonitorCUJ(any());
         doReturn(runner).when(animationRunner).getRunner();
         doReturn(callback).when(animationRunner).getCallback();
 
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index a5687e6..a0f615b 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -3779,17 +3779,25 @@
                 }
                 EventLogTags.writeWmEnterPip(r.mUserId, System.identityHashCode(r),
                         r.shortComponentName, Boolean.toString(isAutoEnter));
-                r.setPictureInPictureParams(params);
-                r.mAutoEnteringPip = isAutoEnter;
-                mRootWindowContainer.moveActivityToPinnedRootTask(r,
-                        null /* launchIntoPipHostActivity */, "enterPictureInPictureMode",
-                        transition);
-                // Continue the pausing process after entering pip.
-                if (r.isState(PAUSING) && r.mPauseSchedulePendingForPip) {
-                    r.getTask().schedulePauseActivity(r, false /* userLeaving */,
-                            false /* pauseImmediately */, true /* autoEnteringPip */, "auto-pip");
+
+                // Ensure the ClientTransactionItems are bundled for this operation.
+                deferWindowLayout();
+                try {
+                    r.setPictureInPictureParams(params);
+                    r.mAutoEnteringPip = isAutoEnter;
+                    mRootWindowContainer.moveActivityToPinnedRootTask(r,
+                            null /* launchIntoPipHostActivity */, "enterPictureInPictureMode",
+                            transition);
+                    // Continue the pausing process after entering pip.
+                    if (r.isState(PAUSING) && r.mPauseSchedulePendingForPip) {
+                        r.getTask().schedulePauseActivity(r, false /* userLeaving */,
+                                false /* pauseImmediately */, true /* autoEnteringPip */,
+                                "auto-pip");
+                    }
+                    r.mAutoEnteringPip = false;
+                } finally {
+                    continueWindowLayout();
                 }
-                r.mAutoEnteringPip = false;
             }
         };
 
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 3eea6ac..e9a877e 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -2378,6 +2378,14 @@
             return false;
         }
 
+        return resumeFocusedTasksTopActivitiesUnchecked(targetRootTask, target, targetOptions,
+                deferPause);
+    }
+
+    @VisibleForTesting
+    boolean resumeFocusedTasksTopActivitiesUnchecked(
+            Task targetRootTask, ActivityRecord target, ActivityOptions targetOptions,
+            boolean deferPause) {
         boolean result = false;
         if (targetRootTask != null && (targetRootTask.isTopRootTaskInDisplayArea()
                 || getTopDisplayFocusedRootTask() == targetRootTask)) {
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index e1ad1be..393a01f 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -69,6 +69,7 @@
 import static com.android.server.wm.ActivityRecord.State.PAUSING;
 import static com.android.server.wm.ActivityRecord.State.RESUMED;
 import static com.android.server.wm.ActivityTaskManagerService.enforceTaskPermission;
+import static com.android.server.wm.ActivityTaskManagerService.isPip2ExperimentEnabled;
 import static com.android.server.wm.ActivityTaskSupervisor.REMOVE_FROM_RECENTS;
 import static com.android.server.wm.Task.FLAG_FORCE_HIDDEN_FOR_PINNED_TASK;
 import static com.android.server.wm.Task.FLAG_FORCE_HIDDEN_FOR_TASK_ORG;
@@ -829,18 +830,20 @@
     }
 
     private int applyTaskChanges(Task tr, WindowContainerTransaction.Change c) {
+        final boolean wasPrevFocusableAndVisible = tr.isFocusableAndVisible();
+
         int effects = applyChanges(tr, c);
         final SurfaceControl.Transaction t = c.getBoundsChangeTransaction();
 
         if ((c.getChangeMask() & WindowContainerTransaction.Change.CHANGE_HIDDEN) != 0) {
             if (tr.setForceHidden(FLAG_FORCE_HIDDEN_FOR_TASK_ORG, c.getHidden())) {
-                effects = TRANSACT_EFFECTS_LIFECYCLE;
+                effects |= TRANSACT_EFFECTS_LIFECYCLE;
             }
         }
 
         if ((c.getChangeMask() & CHANGE_FORCE_TRANSLUCENT) != 0) {
             tr.setForceTranslucent(c.getForceTranslucent());
-            effects = TRANSACT_EFFECTS_LIFECYCLE;
+            effects |= TRANSACT_EFFECTS_LIFECYCLE;
         }
 
         if ((c.getChangeMask() & WindowContainerTransaction.Change.CHANGE_DRAG_RESIZING) != 0) {
@@ -873,8 +876,17 @@
                 boolean canEnterPip = activity.checkEnterPictureInPictureState(
                         "applyTaskChanges", true /* beforeStopping */);
                 if (canEnterPip) {
-                    canEnterPip = mService.mActivityClientController
-                            .requestPictureInPictureMode(activity);
+                    mService.mTaskSupervisor.beginDeferResume();
+                    try {
+                        canEnterPip = mService.mActivityClientController
+                                .requestPictureInPictureMode(activity);
+                    } finally {
+                        mService.mTaskSupervisor.endDeferResume();
+                        if (canEnterPip && !isPip2ExperimentEnabled()) {
+                            // Wait until the transaction is applied to only resume once.
+                            effects |= TRANSACT_EFFECTS_LIFECYCLE;
+                        }
+                    }
                 }
                 if (!canEnterPip) {
                     // Restore the flag to its previous state when the activity cannot enter PIP.
@@ -883,6 +895,11 @@
             }
         }
 
+        // Activity in this Task may resume/pause when enter/exit pip.
+        if (wasPrevFocusableAndVisible != tr.isFocusableAndVisible()) {
+            effects |= TRANSACT_EFFECTS_LIFECYCLE;
+        }
+
         return effects;
     }
 
@@ -948,7 +965,7 @@
         }
         if ((c.getChangeMask() & CHANGE_FORCE_TRANSLUCENT) != 0) {
             taskFragment.setForceTranslucent(c.getForceTranslucent());
-            effects = TRANSACT_EFFECTS_LIFECYCLE;
+            effects |= TRANSACT_EFFECTS_LIFECYCLE;
         }
 
         effects |= applyChanges(taskFragment, c);
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
index 43b424f..69b5c37 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
@@ -1681,7 +1681,8 @@
         WindowContainerToken wct = rootTask.mRemoteToken.toWindowContainerToken();
         t.setWindowingMode(wct, WINDOWING_MODE_PINNED);
         mWm.mAtmService.mWindowOrganizerController.applyTransaction(t);
-        verify(mWm.mAtmService.mRootWindowContainer).resumeFocusedTasksTopActivities();
+        verify(mWm.mAtmService.mRootWindowContainer).resumeFocusedTasksTopActivitiesUnchecked(any(),
+                any(), any(), anyBoolean());
 
         clearInvocations(mWm.mAtmService.mRootWindowContainer);
         // The token for the PIP root task may have changed when the task entered PIP mode, so do
@@ -1690,7 +1691,8 @@
                 record.getRootTask().mRemoteToken.toWindowContainerToken();
         t.setWindowingMode(newToken, WINDOWING_MODE_FULLSCREEN);
         mWm.mAtmService.mWindowOrganizerController.applyTransaction(t);
-        verify(mWm.mAtmService.mRootWindowContainer).resumeFocusedTasksTopActivities();
+        verify(mWm.mAtmService.mRootWindowContainer).resumeFocusedTasksTopActivitiesUnchecked(any(),
+                any(), any(), anyBoolean());
     }
 
     @Test
diff --git a/tests/OneMedia/Android.bp b/tests/OneMedia/Android.bp
index 5c73177..a43cd39 100644
--- a/tests/OneMedia/Android.bp
+++ b/tests/OneMedia/Android.bp
@@ -16,6 +16,7 @@
     platform_apis: true,
     certificate: "platform",
     libs: ["org.apache.http.legacy"],
+    optional_uses_libs: ["org.apache.http.legacy"],
     optimize: {
         enabled: false,
     },