diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java index 3c7db33bccf23..66f5bb663fbeb 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java @@ -68,7 +68,6 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.app.ActivityManager; import android.app.ActivityOptions; -import android.app.ActivityTaskManager; import android.app.PendingIntent; import android.app.WindowConfiguration; import android.content.Context; @@ -522,14 +521,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, finishedCallback.onAnimationFinished(); } }; + Transitions.setRunningRemoteTransitionDelegate(adapter.getCallingApplication()); try { - try { - ActivityTaskManager.getService().setRunningRemoteTransitionDelegate( - adapter.getCallingApplication()); - } catch (SecurityException e) { - Slog.e(TAG, "Unable to boost animation thread. This should only happen" - + " during unit tests"); - } adapter.getRunner().onAnimationStart(transit, apps, wallpapers, augmentedNonApps, wrapCallback); } catch (RemoteException e) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java index ebaece2189aa3..4e1fa290270d3 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java @@ -18,11 +18,9 @@ package com.android.wm.shell.transition; import android.annotation.NonNull; import android.annotation.Nullable; -import android.app.ActivityTaskManager; import android.os.IBinder; import android.os.RemoteException; import android.util.Log; -import android.util.Slog; import android.view.SurfaceControl; import android.window.IRemoteTransition; import android.window.IRemoteTransitionFinishedCallback; @@ -87,17 +85,11 @@ public class OneShotRemoteHandler implements Transitions.TransitionHandler { }); } }; + Transitions.setRunningRemoteTransitionDelegate(mRemote.getAppThread()); try { if (mRemote.asBinder() != null) { mRemote.asBinder().linkToDeath(remoteDied, 0 /* flags */); } - try { - ActivityTaskManager.getService().setRunningRemoteTransitionDelegate( - mRemote.getAppThread()); - } catch (SecurityException e) { - Slog.e(Transitions.TAG, "Unable to boost animation thread. This should only happen" - + " during unit tests"); - } mRemote.getRemoteTransition().startAnimation(transition, info, startTransaction, cb); // assume that remote will apply the start transaction. startTransaction.clear(); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java index b15c48cb58892..cedb340816ae3 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java @@ -18,7 +18,6 @@ package com.android.wm.shell.transition; import android.annotation.NonNull; import android.annotation.Nullable; -import android.app.ActivityTaskManager; import android.os.IBinder; import android.os.RemoteException; import android.util.ArrayMap; @@ -129,15 +128,9 @@ public class RemoteTransitionHandler implements Transitions.TransitionHandler { }); } }; + Transitions.setRunningRemoteTransitionDelegate(remote.getAppThread()); try { handleDeath(remote.asBinder(), finishCallback); - try { - ActivityTaskManager.getService().setRunningRemoteTransitionDelegate( - remote.getAppThread()); - } catch (SecurityException e) { - Log.e(Transitions.TAG, "Unable to boost animation thread. This should only happen" - + " during unit tests"); - } remote.getRemoteTransition().startAnimation(transition, info, startTransaction, cb); // assume that remote will apply the start transaction. startTransaction.clear(); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java index 8e36154d10e7f..881b7a1699f6b 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java @@ -31,6 +31,8 @@ import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTas import android.annotation.NonNull; import android.annotation.Nullable; +import android.app.ActivityTaskManager; +import android.app.IApplicationThread; import android.content.ContentResolver; import android.content.Context; import android.database.ContentObserver; @@ -234,6 +236,19 @@ public class Transitions implements RemoteCallable { mRemoteTransitionHandler.removeFiltered(remoteTransition); } + /** Boosts the process priority of remote animation player. */ + public static void setRunningRemoteTransitionDelegate(IApplicationThread appThread) { + if (appThread == null) return; + try { + ActivityTaskManager.getService().setRunningRemoteTransitionDelegate(appThread); + } catch (SecurityException e) { + Log.e(TAG, "Unable to boost animation process. This should only happen" + + " during unit tests"); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } + } + /** * Runs the given {@code runnable} when the last active transition has finished, or immediately * if there are currently no active transitions. diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java index fe3f37dd4a275..99b34c78597cc 100644 --- a/services/core/java/com/android/server/wm/ActivityStarter.java +++ b/services/core/java/com/android/server/wm/ActivityStarter.java @@ -1661,9 +1661,6 @@ class ActivityStarter { && transitionController.getTransitionPlayer() != null) ? transitionController.createTransition(TRANSIT_OPEN) : null; RemoteTransition remoteTransition = r.takeRemoteTransition(); - if (newTransition != null && remoteTransition != null) { - newTransition.setRemoteTransition(remoteTransition); - } transitionController.collect(r); try { mService.deferWindowLayout(); diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java index 0260b78f49e4c..c8fcee67a383a 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java @@ -5324,7 +5324,13 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { } @Override - public void setRunningRemoteTransitionDelegate(IApplicationThread caller) { + public void setRunningRemoteTransitionDelegate(IApplicationThread delegate) { + final TransitionController controller = getTransitionController(); + // A quick path without entering WM lock. + if (delegate != null && controller.mRemotePlayer.reportRunning(delegate)) { + // The delegate was known as running remote transition. + return; + } mAmInternal.enforceCallingPermission(CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS, "setRunningRemoteTransition"); final int callingPid = Binder.getCallingPid(); @@ -5341,13 +5347,12 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { Slog.e(TAG, msg); throw new SecurityException(msg); } - final WindowProcessController wpc = getProcessController(caller); + final WindowProcessController wpc = getProcessController(delegate); if (wpc == null) { - Slog.w(TAG, "Unable to find process for application " + caller); + Slog.w(TAG, "setRunningRemoteTransition: no process for " + delegate); return; } - wpc.setRunningRemoteAnimation(true /* running */); - callingProc.addRemoteAnimationDelegate(wpc); + controller.mRemotePlayer.update(wpc, true /* running */, false /* predict */); } } diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java index a02be25bc8d2d..5c20258538ee6 100644 --- a/services/core/java/com/android/server/wm/TransitionController.java +++ b/services/core/java/com/android/server/wm/TransitionController.java @@ -45,6 +45,7 @@ import android.window.RemoteTransition; import android.window.TransitionInfo; import android.window.TransitionRequestInfo; +import com.android.internal.annotations.GuardedBy; import com.android.internal.protolog.ProtoLogGroup; import com.android.internal.protolog.common.ProtoLog; import com.android.server.LocalServices; @@ -77,9 +78,10 @@ class TransitionController { final TransitionMetricsReporter mTransitionMetricsReporter = new TransitionMetricsReporter(); final TransitionTracer mTransitionTracer; - private IApplicationThread mTransitionPlayerThread; + private WindowProcessController mTransitionPlayerProc; final ActivityTaskManagerService mAtm; final TaskSnapshotController mTaskSnapshotController; + final RemotePlayer mRemotePlayer; private final ArrayList mLegacyListeners = new ArrayList<>(); @@ -112,6 +114,7 @@ class TransitionController { TaskSnapshotController taskSnapshotController, TransitionTracer transitionTracer) { mAtm = atm; + mRemotePlayer = new RemotePlayer(atm); mStatusBar = LocalServices.getService(StatusBarManagerInternal.class); mTaskSnapshotController = taskSnapshotController; mTransitionTracer = transitionTracer; @@ -123,6 +126,8 @@ class TransitionController { } mPlayingTransitions.clear(); mTransitionPlayer = null; + mTransitionPlayerProc = null; + mRemotePlayer.clear(); mRunningLock.doNotifyLocked(); } }; @@ -169,7 +174,7 @@ class TransitionController { } void registerTransitionPlayer(@Nullable ITransitionPlayer player, - @Nullable IApplicationThread appThread) { + @Nullable WindowProcessController playerProc) { try { // Note: asBinder() can be null if player is same process (likely in a test). if (mTransitionPlayer != null) { @@ -182,7 +187,7 @@ class TransitionController { player.asBinder().linkToDeath(mTransitionPlayerDeath, 0); } mTransitionPlayer = player; - mTransitionPlayerThread = appThread; + mTransitionPlayerProc = playerProc; } catch (RemoteException e) { throw new RuntimeException("Unable to set transition player"); } @@ -421,6 +426,7 @@ class TransitionController { } mTransitionPlayer.requestStartTransition(transition, new TransitionRequestInfo( transition.mType, info, remoteTransition, displayChange)); + transition.setRemoteTransition(remoteTransition); } catch (RemoteException e) { Slog.e(TAG, "Error requesting transition", e); transition.start(); @@ -537,9 +543,7 @@ class TransitionController { } ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Finish Transition: %s", record); mPlayingTransitions.remove(record); - if (mPlayingTransitions.isEmpty()) { - setAnimationRunning(false /* running */); - } + updateRunningRemoteAnimation(record, false /* isPlaying */); record.finishTransition(); mRunningLock.doNotifyLocked(); } @@ -549,21 +553,27 @@ class TransitionController { throw new IllegalStateException("Trying to move non-collecting transition to playing"); } mCollectingTransition = null; - if (mPlayingTransitions.isEmpty()) { - setAnimationRunning(true /* running */); - } mPlayingTransitions.add(transition); + updateRunningRemoteAnimation(transition, true /* isPlaying */); mTransitionTracer.logState(transition); } - private void setAnimationRunning(boolean running) { - if (mTransitionPlayerThread == null) return; - final WindowProcessController wpc = mAtm.getProcessController(mTransitionPlayerThread); - if (wpc == null) { - Slog.w(TAG, "Unable to find process for player thread=" + mTransitionPlayerThread); + /** Updates the process state of animation player. */ + private void updateRunningRemoteAnimation(Transition transition, boolean isPlaying) { + if (mTransitionPlayerProc == null) return; + if (isPlaying) { + mTransitionPlayerProc.setRunningRemoteAnimation(true); + } else if (mPlayingTransitions.isEmpty()) { + mTransitionPlayerProc.setRunningRemoteAnimation(false); + mRemotePlayer.clear(); return; } - wpc.setRunningRemoteAnimation(running); + final RemoteTransition remote = transition.getRemoteTransition(); + if (remote == null) return; + final IApplicationThread appThread = remote.getAppThread(); + final WindowProcessController delegate = mAtm.getProcessController(appThread); + if (delegate == null) return; + mRemotePlayer.update(delegate, isPlaying, true /* predict */); } void abort(Transition transition) { @@ -666,6 +676,95 @@ class TransitionController { proto.end(token); } + /** + * This manages the animating state of processes that are running remote animations for + * {@link #mTransitionPlayerProc}. + */ + static class RemotePlayer { + private static final long REPORT_RUNNING_GRACE_PERIOD_MS = 100; + @GuardedBy("itself") + private final ArrayMap mDelegateProcesses = new ArrayMap<>(); + private final ActivityTaskManagerService mAtm; + + private class DelegateProcess implements Runnable { + final WindowProcessController mProc; + /** Requires {@link RemotePlayer#reportRunning} to confirm it is really running. */ + boolean mNeedReport; + + DelegateProcess(WindowProcessController proc) { + mProc = proc; + } + + /** This runs when the remote player doesn't report running in time. */ + @Override + public void run() { + synchronized (mAtm.mGlobalLockWithoutBoost) { + update(mProc, false /* running */, false /* predict */); + } + } + } + + RemotePlayer(ActivityTaskManagerService atm) { + mAtm = atm; + } + + void update(@NonNull WindowProcessController delegate, boolean running, boolean predict) { + if (!running) { + synchronized (mDelegateProcesses) { + boolean removed = false; + for (int i = mDelegateProcesses.size() - 1; i >= 0; i--) { + if (mDelegateProcesses.valueAt(i).mProc == delegate) { + mDelegateProcesses.removeAt(i); + removed = true; + break; + } + } + if (!removed) return; + } + delegate.setRunningRemoteAnimation(false); + return; + } + if (delegate.isRunningRemoteTransition() || !delegate.hasThread()) return; + delegate.setRunningRemoteAnimation(true); + final DelegateProcess delegateProc = new DelegateProcess(delegate); + // If "predict" is true, that means the remote animation is set from + // ActivityOptions#makeRemoteAnimation(). But it is still up to shell side to decide + // whether to use the remote animation, so there is a timeout to cancel the prediction + // if the remote animation doesn't happen. + if (predict) { + delegateProc.mNeedReport = true; + mAtm.mH.postDelayed(delegateProc, REPORT_RUNNING_GRACE_PERIOD_MS); + } + synchronized (mDelegateProcesses) { + mDelegateProcesses.put(delegate.getThread().asBinder(), delegateProc); + } + } + + void clear() { + synchronized (mDelegateProcesses) { + for (int i = mDelegateProcesses.size() - 1; i >= 0; i--) { + mDelegateProcesses.valueAt(i).mProc.setRunningRemoteAnimation(false); + } + mDelegateProcesses.clear(); + } + } + + /** Returns {@code true} if the app is known to be running remote transition. */ + boolean reportRunning(@NonNull IApplicationThread appThread) { + final DelegateProcess delegate; + synchronized (mDelegateProcesses) { + delegate = mDelegateProcesses.get(appThread.asBinder()); + if (delegate != null && delegate.mNeedReport) { + // It was predicted to run remote transition. Now it is really requesting so + // remove the timeout of restoration. + delegate.mNeedReport = false; + mAtm.mH.removeCallbacks(delegate); + } + } + return delegate != null; + } + } + static class TransitionMetricsReporter extends ITransitionMetricsReporter.Stub { private final ArrayMap mMetricConsumers = new ArrayMap<>(); diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java index 0f5655c74f31f..48740a3c2f506 100644 --- a/services/core/java/com/android/server/wm/WindowOrganizerController.java +++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java @@ -55,7 +55,6 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.app.ActivityManager; import android.app.ActivityOptions; -import android.app.IApplicationThread; import android.app.WindowConfiguration; import android.content.ActivityNotFoundException; import android.content.Intent; @@ -1449,11 +1448,7 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub synchronized (mGlobalLock) { final WindowProcessController wpc = mService.getProcessController(callerPid, callerUid); - IApplicationThread appThread = null; - if (wpc != null) { - appThread = wpc.getThread(); - } - mTransitionController.registerTransitionPlayer(player, appThread); + mTransitionController.registerTransitionPlayer(player, wpc); } } finally { Binder.restoreCallingIdentity(ident); diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java index 3ff912cca091b..87b0c8b779047 100644 --- a/services/core/java/com/android/server/wm/WindowProcessController.java +++ b/services/core/java/com/android/server/wm/WindowProcessController.java @@ -77,7 +77,6 @@ import com.android.server.wm.ActivityTaskManagerService.HotPath; import java.io.IOException; import java.io.PrintWriter; -import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.List; @@ -227,10 +226,6 @@ public class WindowProcessController extends ConfigurationContainer> mRemoteAnimationDelegates = - new ArrayList<>(); - // The bits used for mActivityStateFlags. private static final int ACTIVITY_STATE_FLAG_IS_VISIBLE = 1 << 16; private static final int ACTIVITY_STATE_FLAG_IS_PAUSING_OR_PAUSED = 1 << 17; @@ -1663,30 +1658,7 @@ public class WindowProcessController extends ConfigurationContainer(delegate)); - } - void updateRunningRemoteOrRecentsAnimation() { - if (!isRunningRemoteTransition()) { - // Clean-up any delegates - for (int i = 0; i < mRemoteAnimationDelegates.size(); ++i) { - final WindowProcessController delegate = mRemoteAnimationDelegates.get(i).get(); - if (delegate == null) continue; - delegate.setRunningRemoteAnimation(false); - delegate.setRunningRecentsAnimation(false); - } - mRemoteAnimationDelegates.clear(); - } - // Posting on handler so WM lock isn't held when we call into AM. mAtm.mH.sendMessage(PooledLambda.obtainMessage( WindowProcessListener::setRunningRemoteAnimation, mListener, diff --git a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java index c323e02ad1498..a1ad07adbafb9 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java @@ -62,9 +62,11 @@ import android.util.ArrayMap; import android.util.ArraySet; import android.view.SurfaceControl; import android.window.IDisplayAreaOrganizer; +import android.window.IRemoteTransition; import android.window.ITaskFragmentOrganizer; import android.window.ITaskOrganizer; import android.window.ITransitionPlayer; +import android.window.RemoteTransition; import android.window.TaskFragmentOrganizer; import android.window.TransitionInfo; @@ -412,6 +414,38 @@ public class TransitionTests extends WindowTestsBase { info.getChanges().get(0).getParent()); } + @Test + public void testRunningRemoteTransition() { + final TestTransitionPlayer testPlayer = new TestTransitionPlayer( + mAtm.getTransitionController(), mAtm.mWindowOrganizerController); + final WindowProcessController playerProc = mSystemServicesTestRule.addProcess( + "pkg.player", "proc.player", 5000 /* pid */, 5000 /* uid */); + testPlayer.mController.registerTransitionPlayer(testPlayer, playerProc); + doReturn(mock(IBinder.class)).when(playerProc.getThread()).asBinder(); + final WindowProcessController delegateProc = mSystemServicesTestRule.addProcess( + "pkg.delegate", "proc.delegate", 6000 /* pid */, 6000 /* uid */); + doReturn(mock(IBinder.class)).when(delegateProc.getThread()).asBinder(); + final ActivityRecord app = new ActivityBuilder(mAtm).setCreateTask(true).build(); + final TransitionController controller = app.mTransitionController; + final Transition transition = controller.createTransition(TRANSIT_OPEN); + final RemoteTransition remoteTransition = new RemoteTransition( + mock(IRemoteTransition.class)); + remoteTransition.setAppThread(delegateProc.getThread()); + transition.collectExistenceChange(app.getTask()); + controller.requestStartTransition(transition, app.getTask(), remoteTransition, + null /* displayChange */); + testPlayer.startTransition(); + testPlayer.onTransactionReady(app.getSyncTransaction()); + assertTrue(playerProc.isRunningRemoteTransition()); + assertTrue(delegateProc.isRunningRemoteTransition()); + assertTrue(controller.mRemotePlayer.reportRunning(delegateProc.getThread())); + + testPlayer.finish(); + assertFalse(playerProc.isRunningRemoteTransition()); + assertFalse(delegateProc.isRunningRemoteTransition()); + assertFalse(controller.mRemotePlayer.reportRunning(delegateProc.getThread())); + } + @Test public void testOpenActivityInTheSameTaskWithDisplayChange() { final ActivityRecord closing = createActivityRecord(mDisplayContent); @@ -861,7 +895,7 @@ public class TransitionTests extends WindowTestsBase { final TransitionController controller = new TransitionController(mAtm, snapshotController, mock(TransitionTracer.class)); final ITransitionPlayer player = new ITransitionPlayer.Default(); - controller.registerTransitionPlayer(player, null /* appThread */); + controller.registerTransitionPlayer(player, null /* playerProc */); final Transition openTransition = controller.createTransition(TRANSIT_OPEN); // Start out with task2 visible and set up a transition that closes task2 and opens task1 @@ -926,7 +960,7 @@ public class TransitionTests extends WindowTestsBase { final TransitionController controller = new TransitionController(mAtm, snapshotController, mock(TransitionTracer.class)); final ITransitionPlayer player = new ITransitionPlayer.Default(); - controller.registerTransitionPlayer(player, null /* appThread */); + controller.registerTransitionPlayer(player, null /* playerProc */); final Transition openTransition = controller.createTransition(TRANSIT_OPEN); // Start out with task2 visible and set up a transition that closes task2 and opens task1 @@ -990,7 +1024,7 @@ public class TransitionTests extends WindowTestsBase { final TransitionController controller = new TransitionController(mAtm, snapshotController, mock(TransitionTracer.class)); final ITransitionPlayer player = new ITransitionPlayer.Default(); - controller.registerTransitionPlayer(player, null /* appThread */); + controller.registerTransitionPlayer(player, null /* playerProc */); final Transition openTransition = controller.createTransition(TRANSIT_OPEN); // Start out with task2 visible and set up a transition that closes task2 and opens task1 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 0cbf1b2c7cc85..ee5f36412df23 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java @@ -830,7 +830,7 @@ class WindowTestsBase extends SystemServiceTestsBase { TestTransitionPlayer registerTestTransitionPlayer() { final TestTransitionPlayer testPlayer = new TestTransitionPlayer( mAtm.getTransitionController(), mAtm.mWindowOrganizerController); - testPlayer.mController.registerTransitionPlayer(testPlayer, null /* appThread */); + testPlayer.mController.registerTransitionPlayer(testPlayer, null /* playerProc */); return testPlayer; }