Allow TaskFragmentOrganizer to set remote animation

When all windows in the transition is belong to organized TaskFragments,
have the TaskFragmentOrganizer to control the animation on client side.

Bug: 196173550
Test: atest WmTests:TaskFragmentOrganizerControllerTest
Test: atest WmTests:AppTransitionControllerTest
Change-Id: I835467e23863286a5bd34380424cc9eca2c7372f
diff --git a/core/java/android/window/ITaskFragmentOrganizerController.aidl b/core/java/android/window/ITaskFragmentOrganizerController.aidl
index 0ca8a86..1ad0452 100644
--- a/core/java/android/window/ITaskFragmentOrganizerController.aidl
+++ b/core/java/android/window/ITaskFragmentOrganizerController.aidl
@@ -16,6 +16,7 @@
 
 package android.window;
 
+import android.view.RemoteAnimationDefinition;
 import android.window.ITaskFragmentOrganizer;
 
 /** @hide */
@@ -30,4 +31,17 @@
      * Unregisters a previously registered TaskFragmentOrganizer.
      */
     void unregisterOrganizer(in ITaskFragmentOrganizer organizer);
+
+    /**
+     * Registers remote animations per transition type for the organizer. It will override the
+     * animations if the transition only contains windows that belong to the organized
+     * TaskFragments.
+     */
+    void registerRemoteAnimations(in ITaskFragmentOrganizer organizer,
+        in RemoteAnimationDefinition definition);
+
+    /**
+     * Unregisters remote animations per transition type for the organizer.
+     */
+    void unregisterRemoteAnimations(in ITaskFragmentOrganizer organizer);
 }
diff --git a/core/java/android/window/TaskFragmentOrganizer.java b/core/java/android/window/TaskFragmentOrganizer.java
index f22f0b2..337c5a1 100644
--- a/core/java/android/window/TaskFragmentOrganizer.java
+++ b/core/java/android/window/TaskFragmentOrganizer.java
@@ -23,6 +23,7 @@
 import android.os.Bundle;
 import android.os.IBinder;
 import android.os.RemoteException;
+import android.view.RemoteAnimationDefinition;
 
 import java.util.concurrent.Executor;
 
@@ -90,6 +91,34 @@
         }
     }
 
+    /**
+     * Registers remote animations per transition type for the organizer. It will override the
+     * animations if the transition only contains windows that belong to the organized
+     * TaskFragments.
+     * @hide
+     */
+    @CallSuper
+    public void registerRemoteAnimations(@NonNull RemoteAnimationDefinition definition) {
+        try {
+            getController().registerRemoteAnimations(mInterface, definition);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Unregisters remote animations per transition type for the organizer.
+     * @hide
+     */
+    @CallSuper
+    public void unregisterRemoteAnimations() {
+        try {
+            getController().unregisterRemoteAnimations(mInterface);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
     /** Called when a TaskFragment is created and organized by this organizer. */
     public void onTaskFragmentAppeared(
             @NonNull TaskFragmentAppearedInfo taskFragmentAppearedInfo) {}
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index b49b289..62d0ed2 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -1249,6 +1249,12 @@
       "group": "WM_DEBUG_WINDOW_TRANSITIONS",
       "at": "com\/android\/server\/wm\/Transition.java"
     },
+    "-702650156": {
+      "message": "Override with TaskFragment remote animation for transit=%s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_APP_TRANSITIONS",
+      "at": "com\/android\/server\/wm\/AppTransitionController.java"
+    },
     "-701167286": {
       "message": "applyAnimation: transit=%s, enter=%b, wc=%s",
       "level": "VERBOSE",
@@ -1789,6 +1795,12 @@
       "group": "WM_DEBUG_STATES",
       "at": "com\/android\/server\/wm\/TaskFragment.java"
     },
+    "-70719599": {
+      "message": "Unregister remote animations for organizer=%s uid=%d pid=%d",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_WINDOW_ORGANIZER",
+      "at": "com\/android\/server\/wm\/TaskFragmentOrganizerController.java"
+    },
     "-55185509": {
       "message": "setFocusedTask: taskId=%d touchedActivity=%s",
       "level": "DEBUG",
@@ -2941,6 +2953,12 @@
       "group": "WM_ERROR",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
+    "1210037962": {
+      "message": "Register remote animations for organizer=%s uid=%d pid=%d",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_WINDOW_ORGANIZER",
+      "at": "com\/android\/server\/wm\/TaskFragmentOrganizerController.java"
+    },
     "1219600119": {
       "message": "addWindow: win=%s Callers=%s",
       "level": "DEBUG",
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 67043db..55b7f9c 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -2595,6 +2595,13 @@
         return task != null ? task.getOrganizedTask() : null;
     }
 
+    /** Returns the organized parent {@link TaskFragment}. */
+    @Nullable
+    TaskFragment getOrganizedTaskFragment() {
+        final TaskFragment parent = getTaskFragment();
+        return parent != null ? parent.getOrganizedTaskFragment() : null;
+    }
+
     @Override
     @Nullable
     TaskDisplayArea getDisplayArea() {
diff --git a/services/core/java/com/android/server/wm/AppTransitionController.java b/services/core/java/com/android/server/wm/AppTransitionController.java
index 544cade..7a42351 100644
--- a/services/core/java/com/android/server/wm/AppTransitionController.java
+++ b/services/core/java/com/android/server/wm/AppTransitionController.java
@@ -86,6 +86,7 @@
 import android.view.WindowManager.TransitionOldType;
 import android.view.WindowManager.TransitionType;
 import android.view.animation.Animation;
+import android.window.ITaskFragmentOrganizer;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.protolog.common.ProtoLog;
@@ -233,7 +234,11 @@
         final ActivityRecord topChangingApp =
                 getTopApp(mDisplayContent.mChangingContainers, false /* ignoreHidden */);
         final WindowManager.LayoutParams animLp = getAnimLp(animLpActivity);
-        overrideWithRemoteAnimationIfSet(animLpActivity, transit, activityTypes);
+
+        // Check if there is any override
+        if (!overrideWithTaskFragmentRemoteAnimation(transit, activityTypes)) {
+            overrideWithRemoteAnimationIfSet(animLpActivity, transit, activityTypes);
+        }
 
         final boolean voiceInteraction = containsVoiceInteraction(mDisplayContent.mOpeningApps)
                 || containsVoiceInteraction(mDisplayContent.mOpeningApps);
@@ -483,6 +488,7 @@
         return TYPE_NONE;
     }
 
+    @Nullable
     private static WindowManager.LayoutParams getAnimLp(ActivityRecord activity) {
         final WindowState mainWindow = activity != null ? activity.findMainWindow() : null;
         return mainWindow != null ? mainWindow.mAttrs : null;
@@ -506,6 +512,61 @@
     }
 
     /**
+     * Overrides the pending transition with the remote animation defined by the
+     * {@link ITaskFragmentOrganizer} if all windows in the transition are children of
+     * {@link TaskFragment} that are organized by the same organizer.
+     *
+     * @return {@code true} if the transition is overridden.
+     */
+    @VisibleForTesting
+    boolean overrideWithTaskFragmentRemoteAnimation(@TransitionOldType int transit,
+            ArraySet<Integer> activityTypes) {
+        final ArrayList<WindowContainer> allWindows = new ArrayList<>();
+        allWindows.addAll(mDisplayContent.mClosingApps);
+        allWindows.addAll(mDisplayContent.mOpeningApps);
+        allWindows.addAll(mDisplayContent.mChangingContainers);
+
+        // Find the common TaskFragmentOrganizer of all windows.
+        ITaskFragmentOrganizer organizer = null;
+        for (int i = allWindows.size() - 1; i >= 0; i--) {
+            final ActivityRecord r = getAppFromContainer(allWindows.get(i));
+            if (r == null) {
+                return false;
+            }
+            final TaskFragment organizedTaskFragment = r.getOrganizedTaskFragment();
+            final ITaskFragmentOrganizer curOrganizer = organizedTaskFragment != null
+                    ? organizedTaskFragment.getTaskFragmentOrganizer()
+                    : null;
+            if (curOrganizer == null) {
+                // All windows must below an organized TaskFragment.
+                return false;
+            }
+            if (organizer == null) {
+                organizer = curOrganizer;
+            } else if (!organizer.asBinder().equals(curOrganizer.asBinder())) {
+                // They must be controlled by the same organizer.
+                return false;
+            }
+        }
+
+        final RemoteAnimationDefinition definition = organizer != null
+                ? mDisplayContent.mAtmService.mTaskFragmentOrganizerController
+                    .getRemoteAnimationDefinition(organizer)
+                : null;
+        final RemoteAnimationAdapter adapter = definition != null
+                ? definition.getAdapter(transit, activityTypes)
+                : null;
+        if (adapter == null) {
+            return false;
+        }
+        mDisplayContent.mAppTransition.overridePendingAppTransitionRemote(adapter);
+        ProtoLog.v(WM_DEBUG_APP_TRANSITIONS,
+                "Override with TaskFragment remote animation for transit=%s",
+                AppTransition.appTransitionOldToString(transit));
+        return true;
+    }
+
+    /**
      * Overrides the pending transition with the remote animation defined for the transition in the
      * set of defined remote animations in the app window token.
      */
@@ -524,13 +585,14 @@
     }
 
     static ActivityRecord getAppFromContainer(WindowContainer wc) {
-        return wc.asTask() != null ? wc.asTask().getTopNonFinishingActivity()
+        return wc.asTaskFragment() != null ? wc.asTaskFragment().getTopNonFinishingActivity()
                 : wc.asActivityRecord();
     }
 
     /**
      * @return The window token that determines the animation theme.
      */
+    @Nullable
     private ActivityRecord findAnimLayoutParamsToken(@TransitionOldType int transit,
             ArraySet<Integer> activityTypes) {
         ActivityRecord result;
@@ -543,7 +605,7 @@
                 w -> w.getRemoteAnimationDefinition() != null
                         && w.getRemoteAnimationDefinition().hasTransition(transit, activityTypes));
         if (result != null) {
-            return getAppFromContainer(result);
+            return result;
         }
         result = lookForHighestTokenWithFilter(closingApps, openingApps, changingApps,
                 w -> w.fillsParent() && w.findMainWindow() != null);
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index 242693b..cec4694 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -2095,7 +2095,6 @@
     }
 
     @Nullable
-    @VisibleForTesting
     ITaskFragmentOrganizer getTaskFragmentOrganizer() {
         return mTaskFragmentOrganizer;
     }
diff --git a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
index 690f67c..30d2a32 100644
--- a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
@@ -30,6 +30,7 @@
 import android.os.RemoteException;
 import android.util.ArrayMap;
 import android.util.Slog;
+import android.view.RemoteAnimationDefinition;
 import android.view.SurfaceControl;
 import android.window.ITaskFragmentOrganizer;
 import android.window.ITaskFragmentOrganizerController;
@@ -81,6 +82,13 @@
         private final Map<TaskFragment, Configuration> mLastSentTaskFragmentParentConfigs =
                 new WeakHashMap<>();
 
+        /**
+         * @see android.window.TaskFragmentOrganizer#registerRemoteAnimations(
+         * RemoteAnimationDefinition)
+         */
+        @Nullable
+        private RemoteAnimationDefinition mRemoteAnimationDefinition;
+
         TaskFragmentOrganizerState(ITaskFragmentOrganizer organizer) {
             mOrganizer = organizer;
             try {
@@ -245,6 +253,61 @@
         }
     }
 
+    @Override
+    public void registerRemoteAnimations(ITaskFragmentOrganizer organizer,
+            RemoteAnimationDefinition definition) {
+        final int pid = Binder.getCallingPid();
+        final int uid = Binder.getCallingUid();
+        synchronized (mGlobalLock) {
+            ProtoLog.v(WM_DEBUG_WINDOW_ORGANIZER,
+                    "Register remote animations for organizer=%s uid=%d pid=%d",
+                    organizer.asBinder(), uid, pid);
+            final TaskFragmentOrganizerState organizerState =
+                    mTaskFragmentOrganizerState.get(organizer.asBinder());
+            if (organizerState == null) {
+                throw new IllegalStateException("The organizer hasn't been registered.");
+            }
+            if (organizerState.mRemoteAnimationDefinition != null) {
+                throw new IllegalStateException(
+                        "The organizer has already registered remote animations="
+                                + organizerState.mRemoteAnimationDefinition);
+            }
+
+            definition.setCallingPidUid(pid, uid);
+            organizerState.mRemoteAnimationDefinition = definition;
+        }
+    }
+
+    @Override
+    public void unregisterRemoteAnimations(ITaskFragmentOrganizer organizer) {
+        final int pid = Binder.getCallingPid();
+        final long uid = Binder.getCallingUid();
+        synchronized (mGlobalLock) {
+            ProtoLog.v(WM_DEBUG_WINDOW_ORGANIZER,
+                    "Unregister remote animations for organizer=%s uid=%d pid=%d",
+                    organizer.asBinder(), uid, pid);
+            final TaskFragmentOrganizerState organizerState =
+                    mTaskFragmentOrganizerState.get(organizer.asBinder());
+            if (organizerState == null) {
+                Slog.e(TAG, "The organizer hasn't been registered.");
+                return;
+            }
+
+            organizerState.mRemoteAnimationDefinition = null;
+        }
+    }
+
+    /** Gets the {@link RemoteAnimationDefinition} set on the given organizer if exists. */
+    @Nullable
+    public RemoteAnimationDefinition getRemoteAnimationDefinition(
+            ITaskFragmentOrganizer organizer) {
+        synchronized (mGlobalLock) {
+            final TaskFragmentOrganizerState organizerState =
+                    mTaskFragmentOrganizerState.get(organizer.asBinder());
+            return organizerState != null ? organizerState.mRemoteAnimationDefinition : null;
+        }
+    }
+
     void onTaskFragmentAppeared(ITaskFragmentOrganizer organizer, TaskFragment taskFragment) {
         final TaskFragmentOrganizerState state = validateAndGetState(organizer);
         if (!state.addTaskFragment(taskFragment)) {
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java
index a165e1c..53bae41 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java
@@ -26,15 +26,19 @@
 import static android.view.WindowManager.TRANSIT_OLD_ACTIVITY_OPEN;
 import static android.view.WindowManager.TRANSIT_OLD_KEYGUARD_UNOCCLUDE;
 import static android.view.WindowManager.TRANSIT_OLD_TASK_CHANGE_WINDOWING_MODE;
+import static android.view.WindowManager.TRANSIT_OLD_TASK_FRAGMENT_CHANGE;
 import static android.view.WindowManager.TRANSIT_OLD_TASK_OPEN;
 import static android.view.WindowManager.TRANSIT_OPEN;
 import static android.view.WindowManager.TRANSIT_TO_FRONT;
 
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.Mockito.doCallRealMethod;
+import static org.mockito.Mockito.verify;
 
 import android.os.Binder;
 import android.os.IBinder;
@@ -47,6 +51,8 @@
 import android.view.RemoteAnimationDefinition;
 import android.view.RemoteAnimationTarget;
 import android.view.WindowManager;
+import android.window.ITaskFragmentOrganizer;
+import android.window.TaskFragmentOrganizer;
 
 import androidx.test.filters.SmallTest;
 
@@ -737,4 +743,38 @@
                 mAppTransitionController.getRemoteAnimationOverride(
                         activity, TRANSIT_OLD_ACTIVITY_OPEN, new ArraySet<Integer>()));
     }
+
+    @Test
+    public void testGetRemoteAnimationOverrideTaskFragmentOrganizer() {
+        // TaskFragmentOrganizer registers remote animation.
+        final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
+        final ITaskFragmentOrganizer iOrganizer =
+                ITaskFragmentOrganizer.Stub.asInterface(organizer.getOrganizerToken().asBinder());
+        final RemoteAnimationDefinition definition = new RemoteAnimationDefinition();
+        final RemoteAnimationAdapter adapter = new RemoteAnimationAdapter(
+                new TestRemoteAnimationRunner(), 10, 1);
+        definition.addRemoteAnimation(TRANSIT_OLD_TASK_FRAGMENT_CHANGE, adapter);
+        mAtm.mTaskFragmentOrganizerController.registerOrganizer(iOrganizer);
+        mAtm.mTaskFragmentOrganizerController.registerRemoteAnimations(iOrganizer, definition);
+
+        // Create a TaskFragment with embedded activity.
+        final TaskFragment taskFragment = new TaskFragmentBuilder(mAtm)
+                .setParentTask(createTask(mDisplayContent))
+                .createActivityCount(1)
+                .setOrganizer(organizer)
+                .build();
+        final ActivityRecord activity = taskFragment.getTopMostActivity();
+        activity.allDrawn = true;
+        spyOn(mDisplayContent.mAppTransition);
+
+        // Prepare a transition for TaskFragment.
+        mDisplayContent.mAppTransition.prepareAppTransition(TRANSIT_CHANGE, 0);
+        mDisplayContent.mOpeningApps.add(activity);
+        mDisplayContent.mChangingContainers.add(taskFragment);
+        mDisplayContent.mAppTransitionController.handleAppTransitionReady();
+
+        // Check if the transition has been overridden.
+        verify(mDisplayContent.mAppTransition)
+                .overridePendingAppTransitionRemote(adapter, false /* sync */);
+    }
 }
\ No newline at end of file
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
index 5c79f5c..98c1eabe 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
@@ -21,6 +21,7 @@
 import static com.android.server.wm.testing.Assert.assertThrows;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
 import static org.junit.Assert.fail;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.eq;
@@ -36,6 +37,7 @@
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.platform.test.annotations.Presubmit;
+import android.view.RemoteAnimationDefinition;
 import android.view.SurfaceControl;
 import android.window.ITaskFragmentOrganizer;
 import android.window.TaskFragmentCreationParams;
@@ -70,6 +72,7 @@
     private IBinder mFragmentToken;
     private WindowContainerTransaction mTransaction;
     private WindowContainerToken mFragmentWindowToken;
+    private RemoteAnimationDefinition mDefinition;
 
     @Before
     public void setup() {
@@ -83,6 +86,7 @@
                 new TaskFragment(mAtm, mFragmentToken, true /* createdByOrganizer */);
         mTransaction = new WindowContainerTransaction();
         mFragmentWindowToken = mTaskFragment.mRemoteToken.toWindowContainerToken();
+        mDefinition = new RemoteAnimationDefinition();
 
         spyOn(mController);
         spyOn(mOrganizer);
@@ -208,6 +212,18 @@
     }
 
     @Test
+    public void testRegisterRemoteAnimations() {
+        mController.registerOrganizer(mIOrganizer);
+        mController.registerRemoteAnimations(mIOrganizer, mDefinition);
+
+        assertEquals(mDefinition, mController.getRemoteAnimationDefinition(mIOrganizer));
+
+        mController.unregisterRemoteAnimations(mIOrganizer);
+
+        assertNull(mController.getRemoteAnimationDefinition(mIOrganizer));
+    }
+
+    @Test
     public void testWindowContainerTransaction_setTaskFragmentOrganizer() {
         mOrganizer.applyTransaction(mTransaction);
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
index e7ef6ae..ddb0dad 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -67,6 +67,7 @@
 
 import android.annotation.IntDef;
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.app.ActivityOptions;
 import android.app.IApplicationThread;
@@ -100,6 +101,7 @@
 import android.view.WindowManager.DisplayImePolicy;
 import android.window.ITransitionPlayer;
 import android.window.StartingWindowInfo;
+import android.window.TaskFragmentOrganizer;
 import android.window.TransitionInfo;
 import android.window.TransitionRequestInfo;
 
@@ -1128,6 +1130,8 @@
         private boolean mCreateParentTask;
         private boolean mCreateEmbeddedTask;
         private int mCreateActivityCount = 0;
+        @Nullable
+        private TaskFragmentOrganizer mOrganizer;
 
         TaskFragmentBuilder(ActivityTaskManagerService service) {
             mAtm = service;
@@ -1154,11 +1158,16 @@
             return this;
         }
 
+        TaskFragmentBuilder setOrganizer(@Nullable TaskFragmentOrganizer organizer) {
+            mOrganizer = organizer;
+            return this;
+        }
+
         TaskFragment build() {
             SystemServicesTestRule.checkHoldsLock(mAtm.mGlobalLock);
 
             final TaskFragment taskFragment = new TaskFragment(mAtm, null /* fragmentToken */,
-                    false /* createdByOrganizer */);
+                    mOrganizer != null);
             if (mParentTask == null && mCreateParentTask) {
                 mParentTask = new TaskBuilder(mAtm.mTaskSupervisor).build();
             }
@@ -1176,6 +1185,10 @@
                 taskFragment.addChild(activity);
                 mCreateActivityCount--;
             }
+            if (mOrganizer != null) {
+                taskFragment.setTaskFragmentOrganizer(
+                        mOrganizer.getOrganizerToken(), 10000 /* pid */);
+            }
             return taskFragment;
         }
     }