diff --git a/core/java/android/window/ITaskFragmentOrganizerController.aidl b/core/java/android/window/ITaskFragmentOrganizerController.aidl index 0ca8a864dba53..1ad0452dd837c 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 @@ interface ITaskFragmentOrganizerController { * 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 f22f0b231ec9d..337c5a14e9d3e 100644 --- a/core/java/android/window/TaskFragmentOrganizer.java +++ b/core/java/android/window/TaskFragmentOrganizer.java @@ -23,6 +23,7 @@ import android.content.res.Configuration; 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 @@ public class TaskFragmentOrganizer extends WindowOrganizer { } } + /** + * 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 b49b289d44c0a..62d0ed2dc2d04 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 67043dbab6a38..55b7f9c320bc8 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 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A 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 544cade6578c0..7a42351c33c1d 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.TransitionFlags; 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 @@ public class AppTransitionController { 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 @@ public class AppTransitionController { 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; @@ -505,6 +511,61 @@ public class AppTransitionController { : null; } + /** + * 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 activityTypes) { + final ArrayList 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 @@ public class AppTransitionController { } 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 activityTypes) { ActivityRecord result; @@ -543,7 +605,7 @@ public class AppTransitionController { 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 242693b758fb2..cec4694629cf8 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 @@ class TaskFragment extends WindowContainer { } @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 690f67c25cfe0..30d2a323aabe9 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.IBinder; 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 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr private final Map mLastSentTaskFragmentParentConfigs = new WeakHashMap<>(); + /** + * @see android.window.TaskFragmentOrganizer#registerRemoteAnimations( + * RemoteAnimationDefinition) + */ + @Nullable + private RemoteAnimationDefinition mRemoteAnimationDefinition; + TaskFragmentOrganizerState(ITaskFragmentOrganizer organizer) { mOrganizer = organizer; try { @@ -245,6 +253,61 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr } } + @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 a165e1cf7ad38..53bae4156d6bf 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_CLOSE; 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.RemoteAnimationAdapter; 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 @@ public class AppTransitionControllerTest extends WindowTestsBase { mAppTransitionController.getRemoteAnimationOverride( activity, TRANSIT_OLD_ACTIVITY_OPEN, new ArraySet())); } + + @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 5c79f5ca2c668..98c1eabe4ecaf 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.dx.mockito.inline.extended.ExtendedMockito.spyOn; 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.Binder; 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 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { private IBinder mFragmentToken; private WindowContainerTransaction mTransaction; private WindowContainerToken mFragmentWindowToken; + private RemoteAnimationDefinition mDefinition; @Before public void setup() { @@ -83,6 +86,7 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { new TaskFragment(mAtm, mFragmentToken, true /* createdByOrganizer */); mTransaction = new WindowContainerTransaction(); mFragmentWindowToken = mTaskFragment.mRemoteToken.toWindowContainerToken(); + mDefinition = new RemoteAnimationDefinition(); spyOn(mController); spyOn(mOrganizer); @@ -207,6 +211,18 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { verify(mOrganizer).onTaskFragmentError(eq(errorCallbackToken), eq(exception)); } + @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 e7ef6ae7cc0a3..ddb0dad8b5801 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 static org.mockito.Mockito.mock; 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; 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 @@ class WindowTestsBase extends SystemServiceTestsBase { private boolean mCreateParentTask; private boolean mCreateEmbeddedTask; private int mCreateActivityCount = 0; + @Nullable + private TaskFragmentOrganizer mOrganizer; TaskFragmentBuilder(ActivityTaskManagerService service) { mAtm = service; @@ -1154,11 +1158,16 @@ class WindowTestsBase extends SystemServiceTestsBase { 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 @@ class WindowTestsBase extends SystemServiceTestsBase { taskFragment.addChild(activity); mCreateActivityCount--; } + if (mOrganizer != null) { + taskFragment.setTaskFragmentOrganizer( + mOrganizer.getOrganizerToken(), 10000 /* pid */); + } return taskFragment; } }