diff options
14 files changed, 571 insertions, 117 deletions
diff --git a/core/java/android/window/IRemoteTransition.aidl b/core/java/android/window/IRemoteTransition.aidl index e71de702334a..2efb68a33889 100644 --- a/core/java/android/window/IRemoteTransition.aidl +++ b/core/java/android/window/IRemoteTransition.aidl @@ -40,7 +40,23 @@ oneway interface IRemoteTransition { /** * Starts a transition animation. Once complete, the implementation should call * `finishCallback`. + * + * @param token An identifier for the transition that should be animated. */ - void startAnimation(in TransitionInfo info, in SurfaceControl.Transaction t, + void startAnimation(in IBinder token, in TransitionInfo info, in SurfaceControl.Transaction t, + in IRemoteTransitionFinishedCallback finishCallback); + + /** + * Attempts to merge a transition animation into the animation that is currently + * being played by this remote. If merge is not possible/supported, this should be a no-op. + * If it *is* merged, the implementation should call `finishCallback` immediately. + * + * @param transition An identifier for the transition that wants to be merged. + * @param mergeTarget The transition that is currently being animated by this remote. + * If it can be merged, call `finishCallback`; otherwise, do + * nothing. + */ + void mergeAnimation(in IBinder transition, in TransitionInfo info, + in SurfaceControl.Transaction t, in IBinder mergeTarget, in IRemoteTransitionFinishedCallback finishCallback); } diff --git a/core/java/android/window/ITransitionPlayer.aidl b/core/java/android/window/ITransitionPlayer.aidl index af37fbc6310f..3eca803f3f38 100644 --- a/core/java/android/window/ITransitionPlayer.aidl +++ b/core/java/android/window/ITransitionPlayer.aidl @@ -47,9 +47,12 @@ oneway interface ITransitionPlayer { * @param transitionToken An identifying token for the transition that is now ready to animate. * @param info A collection of all the changes encapsulated by this transition. * @param t A surface transaction containing the surface state prior to animating. + * @param finishT A surface transaction that will reset parenting/layering and generally put + * surfaces into their final (post-transition) state. Apply this after playing + * the animation but before calling finish. */ void onTransitionReady(in IBinder transitionToken, in TransitionInfo info, - in SurfaceControl.Transaction t); + in SurfaceControl.Transaction t, in SurfaceControl.Transaction finishT); /** * Called when something in WMCore requires a transition to play -- for example when an Activity diff --git a/libs/WindowManager/Shell/Android.bp b/libs/WindowManager/Shell/Android.bp index 18f019dc949b..9aaef3b1f655 100644 --- a/libs/WindowManager/Shell/Android.bp +++ b/libs/WindowManager/Shell/Android.bp @@ -129,6 +129,4 @@ android_library { ], kotlincflags: ["-Xjvm-default=enable"], manifest: "AndroidManifest.xml", - - min_sdk_version: "26", } 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 71fd917275ea..4da6664aa3dc 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 @@ -82,18 +82,39 @@ public class OneShotRemoteHandler implements Transitions.TransitionHandler { if (mRemote.asBinder() != null) { mRemote.asBinder().linkToDeath(remoteDied, 0 /* flags */); } - mRemote.startAnimation(info, t, cb); + mRemote.startAnimation(transition, info, t, cb); } catch (RemoteException e) { + Log.e(Transitions.TAG, "Error running remote transition.", e); if (mRemote.asBinder() != null) { mRemote.asBinder().unlinkToDeath(remoteDied, 0 /* flags */); } - Log.e(Transitions.TAG, "Error running remote transition.", e); finishCallback.onTransitionFinished(null /* wct */, null /* wctCB */); } return true; } @Override + public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info, + @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget, + @NonNull Transitions.TransitionFinishCallback finishCallback) { + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Using registered One-shot remote" + + " transition %s for %s.", mRemote, transition); + + IRemoteTransitionFinishedCallback cb = new IRemoteTransitionFinishedCallback.Stub() { + @Override + public void onTransitionFinished(WindowContainerTransaction wct) { + mMainExecutor.execute( + () -> finishCallback.onTransitionFinished(wct, null /* wctCB */)); + } + }; + try { + mRemote.mergeAnimation(transition, info, t, mergeTarget, cb); + } catch (RemoteException e) { + Log.e(Transitions.TAG, "Error merging remote transition.", e); + } + } + + @Override @Nullable public WindowContainerTransaction handleRequest(@NonNull IBinder transition, @Nullable TransitionRequestInfo request) { 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 8876e5f86139..9bfb261fcb85 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 @@ -16,8 +16,6 @@ package com.android.wm.shell.transition; -import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission; - import android.annotation.NonNull; import android.annotation.Nullable; import android.os.IBinder; @@ -52,7 +50,7 @@ public class RemoteTransitionHandler implements Transitions.TransitionHandler { private final ShellExecutor mMainExecutor; /** Includes remotes explicitly requested by, eg, ActivityOptions */ - private final ArrayMap<IBinder, IRemoteTransition> mPendingRemotes = new ArrayMap<>(); + private final ArrayMap<IBinder, IRemoteTransition> mRequestedRemotes = new ArrayMap<>(); /** Ordered by specificity. Last filters will be checked first */ private final ArrayList<Pair<TransitionFilter, IRemoteTransition>> mFilters = @@ -63,9 +61,7 @@ public class RemoteTransitionHandler implements Transitions.TransitionHandler { @Override @BinderThread public void binderDied() { - mMainExecutor.execute(() -> { - mFilters.clear(); - }); + mMainExecutor.execute(() -> mFilters.clear()); } }; @@ -97,10 +93,15 @@ public class RemoteTransitionHandler implements Transitions.TransitionHandler { } @Override + public void onTransitionMerged(@NonNull IBinder transition) { + mRequestedRemotes.remove(transition); + } + + @Override public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction t, @NonNull Transitions.TransitionFinishCallback finishCallback) { - IRemoteTransition pendingRemote = mPendingRemotes.remove(transition); + IRemoteTransition pendingRemote = mRequestedRemotes.get(transition); if (pendingRemote == null) { ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Transition %s doesn't have " + "explicit remote, search filters for match for %s", transition, info); @@ -110,6 +111,8 @@ public class RemoteTransitionHandler implements Transitions.TransitionHandler { mFilters.get(i)); if (mFilters.get(i).first.matches(info)) { pendingRemote = mFilters.get(i).second; + // Add to requested list so that it can be found for merge requests. + mRequestedRemotes.put(transition, pendingRemote); break; } } @@ -122,8 +125,10 @@ public class RemoteTransitionHandler implements Transitions.TransitionHandler { final IRemoteTransition remote = pendingRemote; final IBinder.DeathRecipient remoteDied = () -> { Log.e(Transitions.TAG, "Remote transition died, finishing"); - mMainExecutor.execute( - () -> finishCallback.onTransitionFinished(null /* wct */, null /* wctCB */)); + mMainExecutor.execute(() -> { + mRequestedRemotes.remove(transition); + finishCallback.onTransitionFinished(null /* wct */, null /* wctCB */); + }); }; IRemoteTransitionFinishedCallback cb = new IRemoteTransitionFinishedCallback.Stub() { @Override @@ -131,20 +136,23 @@ public class RemoteTransitionHandler implements Transitions.TransitionHandler { if (remote.asBinder() != null) { remote.asBinder().unlinkToDeath(remoteDied, 0 /* flags */); } - mMainExecutor.execute( - () -> finishCallback.onTransitionFinished(wct, null /* wctCB */)); + mMainExecutor.execute(() -> { + mRequestedRemotes.remove(transition); + finishCallback.onTransitionFinished(wct, null /* wctCB */); + }); } }; try { if (remote.asBinder() != null) { remote.asBinder().linkToDeath(remoteDied, 0 /* flags */); } - remote.startAnimation(info, t, cb); + remote.startAnimation(transition, info, t, cb); } catch (RemoteException e) { + Log.e(Transitions.TAG, "Error running remote transition.", e); if (remote.asBinder() != null) { remote.asBinder().unlinkToDeath(remoteDied, 0 /* flags */); } - Log.e(Transitions.TAG, "Error running remote transition.", e); + mRequestedRemotes.remove(transition); mMainExecutor.execute( () -> finishCallback.onTransitionFinished(null /* wct */, null /* wctCB */)); } @@ -152,12 +160,42 @@ public class RemoteTransitionHandler implements Transitions.TransitionHandler { } @Override + public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info, + @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget, + @NonNull Transitions.TransitionFinishCallback finishCallback) { + final IRemoteTransition remote = mRequestedRemotes.get(mergeTarget); + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Attempt merge %s into %s", + transition, remote); + if (remote == null) return; + + IRemoteTransitionFinishedCallback cb = new IRemoteTransitionFinishedCallback.Stub() { + @Override + public void onTransitionFinished(WindowContainerTransaction wct) { + mMainExecutor.execute(() -> { + if (!mRequestedRemotes.containsKey(mergeTarget)) { + Log.e(TAG, "Merged transition finished after it's mergeTarget (the " + + "transition it was supposed to merge into). This usually means " + + "that the mergeTarget's RemoteTransition impl erroneously " + + "accepted/ran the merge request after finishing the mergeTarget"); + } + finishCallback.onTransitionFinished(wct, null /* wctCB */); + }); + } + }; + try { + remote.mergeAnimation(transition, info, t, mergeTarget, cb); + } catch (RemoteException e) { + Log.e(Transitions.TAG, "Error attempting to merge remote transition.", e); + } + } + + @Override @Nullable public WindowContainerTransaction handleRequest(@NonNull IBinder transition, @Nullable TransitionRequestInfo request) { IRemoteTransition remote = request.getRemoteTransition(); if (remote == null) return null; - mPendingRemotes.put(transition, remote); + mRequestedRemotes.put(transition, remote); ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "RemoteTransition directly requested" + " for %s: %s", transition, remote); return new WindowContainerTransaction(); 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 64dc47f5549a..3251abc3a8b8 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 @@ -35,7 +35,6 @@ import android.os.IBinder; import android.os.RemoteException; import android.os.SystemProperties; import android.provider.Settings; -import android.util.ArrayMap; import android.util.Log; import android.view.SurfaceControl; import android.view.WindowManager; @@ -91,11 +90,16 @@ public class Transitions implements RemoteCallable<Transitions> { private float mTransitionAnimationScaleSetting = 1.0f; private static final class ActiveTransition { - TransitionHandler mFirstHandler = null; + IBinder mToken = null; + TransitionHandler mHandler = null; + boolean mMerged = false; + TransitionInfo mInfo = null; + SurfaceControl.Transaction mStartT = null; + SurfaceControl.Transaction mFinishT = null; } - /** Keeps track of currently tracked transitions and all the animations associated with each */ - private final ArrayMap<IBinder, ActiveTransition> mActiveTransitions = new ArrayMap<>(); + /** Keeps track of currently playing transitions in the order of receipt. */ + private final ArrayList<ActiveTransition> mActiveTransitions = new ArrayList<>(); public Transitions(@NonNull WindowOrganizer organizer, @NonNull TransactionPool pool, @NonNull Context context, @NonNull ShellExecutor mainExecutor, @@ -226,7 +230,7 @@ public class Transitions implements RemoteCallable<Transitions> { * type, their transit mode, and their destination z-order. */ private static void setupStartState(@NonNull TransitionInfo info, - @NonNull SurfaceControl.Transaction t) { + @NonNull SurfaceControl.Transaction t, @NonNull SurfaceControl.Transaction finishT) { boolean isOpening = isOpeningType(info.getType()); if (info.getRootLeash().isValid()) { t.show(info.getRootLeash()); @@ -270,6 +274,8 @@ public class Transitions implements RemoteCallable<Transitions> { t.setAlpha(leash, 1.f); } else { t.setAlpha(leash, 0.f); + // fix alpha in finish transaction in case the animator itself no-ops. + finishT.setAlpha(leash, 1.f); } } else { // put on bottom and leave it visible @@ -290,16 +296,24 @@ public class Transitions implements RemoteCallable<Transitions> { } } + private int findActiveTransition(IBinder token) { + for (int i = mActiveTransitions.size() - 1; i >= 0; --i) { + if (mActiveTransitions.get(i).mToken == token) return i; + } + return -1; + } + @VisibleForTesting void onTransitionReady(@NonNull IBinder transitionToken, @NonNull TransitionInfo info, - @NonNull SurfaceControl.Transaction t) { + @NonNull SurfaceControl.Transaction t, @NonNull SurfaceControl.Transaction finishT) { ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "onTransitionReady %s: %s", transitionToken, info); - final ActiveTransition active = mActiveTransitions.get(transitionToken); - if (active == null) { + final int activeIdx = findActiveTransition(transitionToken); + if (activeIdx < 0) { throw new IllegalStateException("Got transitionReady for non-active transition " + transitionToken + ". expecting one of " - + Arrays.toString(mActiveTransitions.keySet().toArray())); + + Arrays.toString(mActiveTransitions.stream().map( + activeTransition -> activeTransition.mToken).toArray())); } if (!info.getRootLeash().isValid()) { // Invalid root-leash implies that the transition is empty/no-op, so just do @@ -307,30 +321,62 @@ public class Transitions implements RemoteCallable<Transitions> { ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Invalid root leash (%s): %s", transitionToken, info); t.apply(); - onFinish(transitionToken, null /* wct */, null /* wctCB */); + onAbort(transitionToken); return; } - setupStartState(info, t); + final ActiveTransition active = mActiveTransitions.get(activeIdx); + active.mInfo = info; + active.mStartT = t; + active.mFinishT = finishT; + if (activeIdx > 0) { + // This is now playing at the same time as an existing animation, so try merging it. + attemptMergeTransition(mActiveTransitions.get(0), active); + return; + } + // The normal case, just play it. + playTransition(active); + } - final TransitionFinishCallback finishCb = (wct, cb) -> onFinish(transitionToken, wct, cb); - // If a handler chose to uniquely run this animation, try delegating to it. - if (active.mFirstHandler != null) { + /** + * Attempt to merge by delegating the transition start to the handler of the currently + * playing transition. + */ + void attemptMergeTransition(@NonNull ActiveTransition playing, + @NonNull ActiveTransition merging) { + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Transition %s ready while" + + " another transition %s is still animating. Notify the animating transition" + + " in case they can be merged", merging.mToken, playing.mToken); + playing.mHandler.mergeAnimation(merging.mToken, merging.mInfo, merging.mStartT, + playing.mToken, (wct, cb) -> onFinish(merging.mToken, wct, cb)); + } + + boolean startAnimation(@NonNull ActiveTransition active, TransitionHandler handler) { + return handler.startAnimation(active.mToken, active.mInfo, active.mStartT, + (wct, cb) -> onFinish(active.mToken, wct, cb)); + } + + void playTransition(@NonNull ActiveTransition active) { + setupStartState(active.mInfo, active.mStartT, active.mFinishT); + + // If a handler already chose to run this animation, try delegating to it first. + if (active.mHandler != null) { ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " try firstHandler %s", - active.mFirstHandler); - if (active.mFirstHandler.startAnimation(transitionToken, info, t, finishCb)) { + active.mHandler); + if (startAnimation(active, active.mHandler)) { ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " animated by firstHandler"); return; } } // Otherwise give every other handler a chance (in order) for (int i = mHandlers.size() - 1; i >= 0; --i) { - if (mHandlers.get(i) == active.mFirstHandler) continue; + if (mHandlers.get(i) == active.mHandler) continue; ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " try handler %s", mHandlers.get(i)); - if (mHandlers.get(i).startAnimation(transitionToken, info, t, finishCb)) { + if (startAnimation(active, mHandlers.get(i))) { ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " animated by %s", mHandlers.get(i)); + active.mHandler = mHandlers.get(i); return; } } @@ -338,23 +384,107 @@ public class Transitions implements RemoteCallable<Transitions> { "This shouldn't happen, maybe the default handler is broken."); } - private void onFinish(IBinder transition, @Nullable WindowContainerTransaction wct, + /** Special version of finish just for dealing with no-op/invalid transitions. */ + private void onAbort(IBinder transition) { + final int activeIdx = findActiveTransition(transition); + if (activeIdx < 0) return; + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, + "Transition animation aborted due to no-op, notifying core %s", transition); + mActiveTransitions.remove(activeIdx); + mOrganizer.finishTransition(transition, null /* wct */, null /* wctCB */); + } + + private void onFinish(IBinder transition, + @Nullable WindowContainerTransaction wct, @Nullable WindowContainerTransactionCallback wctCB) { - if (!mActiveTransitions.containsKey(transition)) { - Log.e(TAG, "Trying to finish a non-running transition. Maybe remote crashed?"); + int activeIdx = findActiveTransition(transition); + if (activeIdx < 0) { + Log.e(TAG, "Trying to finish a non-running transition. Either remote crashed or " + + " a handler didn't properly deal with a merge.", new RuntimeException()); + return; + } else if (activeIdx > 0) { + // This transition was merged. + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Transition was merged: %s", + transition); + final ActiveTransition active = mActiveTransitions.get(activeIdx); + active.mMerged = true; + if (active.mHandler != null) { + active.mHandler.onTransitionMerged(active.mToken); + } return; } ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, - "Transition animations finished, notifying core %s", transition); - mActiveTransitions.remove(transition); + "Transition animation finished, notifying core %s", transition); + // Merge all relevant transactions together + SurfaceControl.Transaction fullFinish = mActiveTransitions.get(activeIdx).mFinishT; + for (int iA = activeIdx + 1; iA < mActiveTransitions.size(); ++iA) { + final ActiveTransition toMerge = mActiveTransitions.get(iA); + if (!toMerge.mMerged) break; + // Include start. It will be a no-op if it was already applied. Otherwise, we need it + // to maintain consistent state. + fullFinish.merge(mActiveTransitions.get(iA).mStartT); + fullFinish.merge(mActiveTransitions.get(iA).mFinishT); + } + fullFinish.apply(); + // Now perform all the finishes. + mActiveTransitions.remove(activeIdx); mOrganizer.finishTransition(transition, wct, wctCB); + while (activeIdx < mActiveTransitions.size()) { + if (!mActiveTransitions.get(activeIdx).mMerged) break; + ActiveTransition merged = mActiveTransitions.remove(activeIdx); + mOrganizer.finishTransition(merged.mToken, null /* wct */, null /* wctCB */); + } + if (mActiveTransitions.size() <= activeIdx) { + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "All active transition animations " + + "finished"); + return; + } + // Start animating the next active transition + final ActiveTransition next = mActiveTransitions.get(activeIdx); + if (next.mInfo == null) { + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Pending transition after one" + + " finished, but it isn't ready yet."); + return; + } + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Pending transitions after one" + + " finished, so start the next one."); + playTransition(next); + // Now try to merge the rest of the transitions (re-acquire activeIdx since next may have + // finished immediately) + activeIdx = findActiveTransition(next.mToken); + if (activeIdx < 0) { + // This means 'next' finished immediately and thus re-entered this function. Since + // that is the case, just return here since all relevant logic has already run in the + // re-entered call. + return; + } + + // This logic is also convoluted because 'next' may finish immediately in response to any of + // the merge requests (eg. if it decided to "cancel" itself). + int mergeIdx = activeIdx + 1; + while (mergeIdx < mActiveTransitions.size()) { + ActiveTransition mergeCandidate = mActiveTransitions.get(mergeIdx); + if (mergeCandidate.mMerged) { + throw new IllegalStateException("Can't merge a transition after not-merging" + + " a preceding one."); + } + attemptMergeTransition(next, mergeCandidate); + mergeIdx = findActiveTransition(mergeCandidate.mToken); + if (mergeIdx < 0) { + // This means 'next' finished immediately and thus re-entered this function. Since + // that is the case, just return here since all relevant logic has already run in + // the re-entered call. + return; + } + ++mergeIdx; + } } void requestStartTransition(@NonNull IBinder transitionToken, @Nullable TransitionRequestInfo request) { ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Transition requested: %s %s", transitionToken, request); - if (mActiveTransitions.containsKey(transitionToken)) { + if (findActiveTransition(transitionToken) >= 0) { throw new RuntimeException("Transition already started " + transitionToken); } final ActiveTransition active = new ActiveTransition(); @@ -362,23 +492,23 @@ public class Transitions implements RemoteCallable<Transitions> { for (int i = mHandlers.size() - 1; i >= 0; --i) { wct = mHandlers.get(i).handleRequest(transitionToken, request); if (wct != null) { - active.mFirstHandler = mHandlers.get(i); + active.mHandler = mHandlers.get(i); break; } } - IBinder transition = mOrganizer.startTransition( + active.mToken = mOrganizer.startTransition( request.getType(), transitionToken, wct); - mActiveTransitions.put(transition, active); + mActiveTransitions.add(active); } /** Start a new transition directly. */ public IBinder startTransition(@WindowManager.TransitionType int type, @NonNull WindowContainerTransaction wct, @Nullable TransitionHandler handler) { final ActiveTransition active = new ActiveTransition(); - active.mFirstHandler = handler; - IBinder transition = mOrganizer.startTransition(type, null /* token */, wct); - mActiveTransitions.put(transition, active); - return transition; + active.mHandler = handler; + active.mToken = mOrganizer.startTransition(type, null /* token */, wct); + mActiveTransitions.add(active); + return active.mToken; } /** @@ -415,6 +545,32 @@ public class Transitions implements RemoteCallable<Transitions> { @NonNull TransitionFinishCallback finishCallback); /** + * Attempts to merge a different transition's animation into an animation that this handler + * is currently playing. If a merge is not possible/supported, this should be a no-op. + * + * This gets called if another transition becomes ready while this handler is still playing + * an animation. This is called regardless of whether this handler claims to support that + * particular transition or not. + * + * When this happens, there are 2 options: + * 1. Do nothing. This effectively rejects the merge request. This is the "safest" option. + * 2. Merge the incoming transition into this one. The implementation is up to this + * handler. To indicate that this handler has "consumed" the merge transition, it + * must call the finishCallback immediately, or at-least before the original + * transition's finishCallback is called. + * + * @param transition This is the transition that wants to be merged. + * @param info Information about what is changing in the transition. + * @param t Contains surface changes that resulted from the transition. + * @param mergeTarget This is the transition that we are attempting to merge with (ie. the + * one this handler is currently already animating). + * @param finishCallback Call this if merged. This MUST be called on main thread. + */ + default void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info, + @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget, + @NonNull TransitionFinishCallback finishCallback) { } + + /** * Potentially handles a startTransition request. * * @param transition The transition whose start is being requested. @@ -427,6 +583,12 @@ public class Transitions implements RemoteCallable<Transitions> { @NonNull TransitionRequestInfo request); /** + * Called when a transition which was already "claimed" by this handler has been merged + * into another animation. Gives this handler a chance to clean-up any expectations. + */ + default void onTransitionMerged(@NonNull IBinder transition) { } + + /** * Sets transition animation scale settings value to handler. * * @param scale The setting value of transition animation scale. @@ -438,10 +600,10 @@ public class Transitions implements RemoteCallable<Transitions> { private class TransitionPlayerImpl extends ITransitionPlayer.Stub { @Override public void onTransitionReady(IBinder iBinder, TransitionInfo transitionInfo, - SurfaceControl.Transaction transaction) throws RemoteException { - mMainExecutor.execute(() -> { - Transitions.this.onTransitionReady(iBinder, transitionInfo, transaction); - }); + SurfaceControl.Transaction t, SurfaceControl.Transaction finishT) + throws RemoteException { + mMainExecutor.execute(() -> Transitions.this.onTransitionReady( + iBinder, transitionInfo, t, finishT)); } @Override diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java index 18642fcfef6c..08ac2a6cfa77 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java @@ -332,11 +332,18 @@ public class SplitTransitionTests extends ShellTestCase { final WindowContainerTransaction mRemoteFinishWCT = new WindowContainerTransaction(); @Override - public void startAnimation(TransitionInfo info, SurfaceControl.Transaction t, - IRemoteTransitionFinishedCallback finishCallback) throws RemoteException { + public void startAnimation(IBinder transition, TransitionInfo info, + SurfaceControl.Transaction t, IRemoteTransitionFinishedCallback finishCallback) + throws RemoteException { mCalled = true; finishCallback.onTransitionFinished(mRemoteFinishWCT); } + + @Override + public void mergeAnimation(IBinder transition, TransitionInfo info, + SurfaceControl.Transaction t, IBinder mergeTarget, + IRemoteTransitionFinishedCallback finishCallback) throws RemoteException { + } } } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java index c1733de75535..2d2ab2c9f674 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java @@ -107,7 +107,8 @@ public class ShellTransitionTests { verify(mOrganizer, times(1)).startTransition(eq(TRANSIT_OPEN), eq(transitToken), any()); TransitionInfo info = new TransitionInfoBuilder(TRANSIT_OPEN) .addChange(TRANSIT_OPEN).addChange(TRANSIT_CLOSE).build(); - transitions.onTransitionReady(transitToken, info, mock(SurfaceControl.Transaction.class)); + transitions.onTransitionReady(transitToken, info, mock(SurfaceControl.Transaction.class), + mock(SurfaceControl.Transaction.class)); assertEquals(1, mDefaultHandler.activeCount()); mDefaultHandler.finishAll(); mMainExecutor.flushAll(); @@ -155,7 +156,8 @@ public class ShellTransitionTests { transitions.requestStartTransition(transitToken, new TransitionRequestInfo(TRANSIT_OPEN, null /* trigger */, null /* remote */)); verify(mOrganizer, times(1)).startTransition(eq(TRANSIT_OPEN), eq(transitToken), isNull()); - transitions.onTransitionReady(transitToken, open, mock(SurfaceControl.Transaction.class)); + transitions.onTransitionReady(transitToken, open, mock(SurfaceControl.Transaction.class), + mock(SurfaceControl.Transaction.class)); assertEquals(1, mDefaultHandler.activeCount()); assertEquals(0, testHandler.activeCount()); mDefaultHandler.finishAll(); @@ -168,7 +170,8 @@ public class ShellTransitionTests { new TransitionRequestInfo(TRANSIT_OPEN, mwTaskInfo, null /* remote */)); verify(mOrganizer, times(1)).startTransition( eq(TRANSIT_OPEN), eq(transitToken), eq(handlerWCT)); - transitions.onTransitionReady(transitToken, open, mock(SurfaceControl.Transaction.class)); + transitions.onTransitionReady(transitToken, open, mock(SurfaceControl.Transaction.class), + mock(SurfaceControl.Transaction.class)); assertEquals(1, mDefaultHandler.activeCount()); assertEquals(0, testHandler.activeCount()); mDefaultHandler.finishAll(); @@ -185,7 +188,8 @@ public class ShellTransitionTests { eq(TRANSIT_CHANGE), eq(transitToken), eq(handlerWCT)); TransitionInfo change = new TransitionInfoBuilder(TRANSIT_CHANGE) .addChange(TRANSIT_CHANGE).build(); - transitions.onTransitionReady(transitToken, change, mock(SurfaceControl.Transaction.class)); + transitions.onTransitionReady(transitToken, change, mock(SurfaceControl.Transaction.class), + mock(SurfaceControl.Transaction.class)); assertEquals(0, mDefaultHandler.activeCount()); assertEquals(1, testHandler.activeCount()); assertEquals(0, topHandler.activeCount()); @@ -203,11 +207,18 @@ public class ShellTransitionTests { final WindowContainerTransaction remoteFinishWCT = new WindowContainerTransaction(); IRemoteTransition testRemote = new IRemoteTransition.Stub() { @Override - public void startAnimation(TransitionInfo info, SurfaceControl.Transaction t, + public void startAnimation(IBinder token, TransitionInfo info, + SurfaceControl.Transaction t, IRemoteTransitionFinishedCallback finishCallback) throws RemoteException { remoteCalled[0] = true; finishCallback.onTransitionFinished(remoteFinishWCT); } + + @Override + public void mergeAnimation(IBinder token, TransitionInfo info, + SurfaceControl.Transaction t, IBinder mergeTarget, + IRemoteTransitionFinishedCallback finishCallback) throws RemoteException { + } }; IBinder transitToken = new Binder(); transitions.requestStartTransition(transitToken, @@ -215,7 +226,8 @@ public class ShellTransitionTests { verify(mOrganizer, times(1)).startTransition(eq(TRANSIT_OPEN), eq(transitToken), any()); TransitionInfo info = new TransitionInfoBuilder(TRANSIT_OPEN) .addChange(TRANSIT_OPEN).addChange(TRANSIT_CLOSE).build(); - transitions.onTransitionReady(transitToken, info, mock(SurfaceControl.Transaction.class)); + transitions.onTransitionReady(transitToken, info, mock(SurfaceControl.Transaction.class), + mock(SurfaceControl.Transaction.class)); assertEquals(0, mDefaultHandler.activeCount()); assertTrue(remoteCalled[0]); mDefaultHandler.finishAll(); @@ -269,11 +281,18 @@ public class ShellTransitionTests { final boolean[] remoteCalled = new boolean[]{false}; IRemoteTransition testRemote = new IRemoteTransition.Stub() { @Override - public void startAnimation(TransitionInfo info, SurfaceControl.Transaction t, + public void startAnimation(IBinder token, TransitionInfo info, + SurfaceControl.Transaction t, IRemoteTransitionFinishedCallback finishCallback) throws RemoteException { remoteCalled[0] = true; finishCallback.onTransitionFinished(null /* wct */); } + + @Override + public void mergeAnimation(IBinder token, TransitionInfo info, + SurfaceControl.Transaction t, IBinder mergeTarget, + IRemoteTransitionFinishedCallback finishCallback) throws RemoteException { + } }; TransitionFilter filter = new TransitionFilter(); @@ -290,7 +309,8 @@ public class ShellTransitionTests { verify(mOrganizer, times(1)).startTransition(eq(TRANSIT_OPEN), eq(transitToken), any()); TransitionInfo info = new TransitionInfoBuilder(TRANSIT_OPEN) .addChange(TRANSIT_OPEN).addChange(TRANSIT_CLOSE).build(); - transitions.onTransitionReady(transitToken, info, mock(SurfaceControl.Transaction.class)); + transitions.onTransitionReady(transitToken, info, mock(SurfaceControl.Transaction.class), + mock(SurfaceControl.Transaction.class)); assertEquals(0, mDefaultHandler.activeCount()); assertTrue(remoteCalled[0]); mDefaultHandler.finishAll(); @@ -308,11 +328,18 @@ public class ShellTransitionTests { final WindowContainerTransaction remoteFinishWCT = new WindowContainerTransaction(); IRemoteTransition testRemote = new IRemoteTransition.Stub() { @Override - public void startAnimation(TransitionInfo info, SurfaceControl.Transaction t, + public void startAnimation(IBinder token, TransitionInfo info, + SurfaceControl.Transaction t, IRemoteTransitionFinishedCallback finishCallback) throws RemoteException { remoteCalled[0] = true; finishCallback.onTransitionFinished(remoteFinishWCT); } + + @Override + public void mergeAnimation(IBinder token, TransitionInfo info, + SurfaceControl.Transaction t, IBinder mergeTarget, + IRemoteTransitionFinishedCallback finishCallback) throws RemoteException { + } }; final int transitType = TRANSIT_FIRST_CUSTOM + 1; @@ -336,6 +363,86 @@ public class ShellTransitionTests { mock(SurfaceControl.Transaction.class), testFinish)); } + @Test + public void testTransitionQueueing() { + Transitions transitions = new Transitions(mOrganizer, mTransactionPool, mContext, + mMainExecutor, mAnimExecutor); + transitions.replaceDefaultHandlerForTest(mDefaultHandler); + + IBinder transitToken1 = new Binder(); + transitions.requestStartTransition(transitToken1, + new TransitionRequestInfo(TRANSIT_OPEN, null /* trigger */, null /* remote */)); + TransitionInfo info1 = new TransitionInfoBuilder(TRANSIT_OPEN) + .addChange(TRANSIT_OPEN).addChange(TRANSIT_CLOSE).build(); + transitions.onTransitionReady(transitToken1, info1, mock(SurfaceControl.Transaction.class), + mock(SurfaceControl.Transaction.class)); + assertEquals(1, mDefaultHandler.activeCount()); + + IBinder transitToken2 = new Binder(); + transitions.requestStartTransition(transitToken2, + new TransitionRequestInfo(TRANSIT_CLOSE, null /* trigger */, null /* remote */)); + TransitionInfo info2 = new TransitionInfoBuilder(TRANSIT_CLOSE) + .addChange(TRANSIT_OPEN).addChange(TRANSIT_CLOSE).build(); + transitions.onTransitionReady(transitToken2, info2, mock(SurfaceControl.Transaction.class), + mock(SurfaceControl.Transaction.class)); + // default handler doesn't merge by default, so it shouldn't increment active count. + assertEquals(1, mDefaultHandler.activeCount()); + assertEquals(0, mDefaultHandler.mergeCount()); + verify(mOrganizer, times(0)).finishTransition(eq(transitToken1), any(), any()); + verify(mOrganizer, times(0)).finishTransition(eq(transitToken2), any(), any()); + + mDefaultHandler.finishAll(); + mMainExecutor.flushAll(); + // first transition finished + verify(mOrganizer, times(1)).finishTransition(eq(transitToken1), any(), any()); + verify(mOrganizer, times(0)).finishTransition(eq(transitToken2), any(), any()); + // But now the "queued" transition is running + assertEquals(1, mDefaultHandler.activeCount()); + + mDefaultHandler.finishAll(); + mMainExecutor.flushAll(); + verify(mOrganizer, times(1)).finishTransition(eq(transitToken2), any(), any()); + } + + @Test + public void testTransitionMerging() { + Transitions transitions = new Transitions(mOrganizer, mTransactionPool, mContext, + mMainExecutor, mAnimExecutor); + mDefaultHandler.setSimulateMerge(true); + transitions.replaceDefaultHandlerForTest(mDefaultHandler); + + IBinder transitToken1 = new Binder(); + transitions.requestStartTransition(transitToken1, + new TransitionRequestInfo(TRANSIT_OPEN, null /* trigger */, null /* remote */)); + TransitionInfo info1 = new TransitionInfoBuilder(TRANSIT_OPEN) + .addChange(TRANSIT_OPEN).addChange(TRANSIT_CLOSE).build(); + transitions.onTransitionReady(transitToken1, info1, mock(SurfaceControl.Transaction.class), + mock(SurfaceControl.Transaction.class)); + assertEquals(1, mDefaultHandler.activeCount()); + + IBinder transitToken2 = new Binder(); + transitions.requestStartTransition(transitToken2, + new TransitionRequestInfo(TRANSIT_CLOSE, null /* trigger */, null /* remote */)); + TransitionInfo info2 = new TransitionInfoBuilder(TRANSIT_CLOSE) + .addChange(TRANSIT_OPEN).addChange(TRANSIT_CLOSE).build(); + transitions.onTransitionReady(transitToken2, info2, mock(SurfaceControl.Transaction.class), + mock(SurfaceControl.Transaction.class)); + // it should still only have 1 active, but then show 1 merged + assertEquals(1, mDefaultHandler.activeCount()); + assertEquals(1, mDefaultHandler.mergeCount()); + verify(mOrganizer, times(0)).finishTransition(eq(transitToken1), any(), any()); + // We don't tell organizer it is finished yet (since we still want to maintain ordering) + verify(mOrganizer, times(0)).finishTransition(eq(transitToken2), any(), any()); + + mDefaultHandler.finishAll(); + mMainExecutor.flushAll(); + // transition + merged all finished. + verify(mOrganizer, times(1)).finishTransition(eq(transitToken1), any(), any()); + verify(mOrganizer, times(1)).finishTransition(eq(transitToken2), any(), any()); + // Make sure nothing was queued + assertEquals(0, mDefaultHandler.activeCount()); + } + class TransitionInfoBuilder { final TransitionInfo mInfo; @@ -364,7 +471,9 @@ public class ShellTransitionTests { } class TestTransitionHandler implements Transitions.TransitionHandler { - final ArrayList<Transitions.TransitionFinishCallback> mFinishes = new ArrayList<>(); + ArrayList<Transitions.TransitionFinishCallback> mFinishes = new ArrayList<>(); + final ArrayList<IBinder> mMerged = new ArrayList<>(); + boolean mSimulateMerge = false; @Override public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info, @@ -374,6 +483,15 @@ public class ShellTransitionTests { return true; } + @Override + public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info, + @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget, + @NonNull Transitions.TransitionFinishCallback finishCallback) { + if (!mSimulateMerge) return; + mMerged.add(transition); + finishCallback.onTransitionFinished(null /* wct */, null /* wctCB */); + } + @Nullable @Override public WindowContainerTransaction handleRequest(@NonNull IBinder transition, @@ -381,16 +499,25 @@ public class ShellTransitionTests { return null; } + void setSimulateMerge(boolean sim) { + mSimulateMerge = sim; + } + void finishAll() { - for (int i = mFinishes.size() - 1; i >= 0; --i) { - mFinishes.get(i).onTransitionFinished(null /* wct */, null /* wctCB */); + final ArrayList<Transitions.TransitionFinishCallback> finishes = mFinishes; + mFinishes = new ArrayList<>(); + for (int i = finishes.size() - 1; i >= 0; --i) { + finishes.get(i).onTransitionFinished(null /* wct */, null /* wctCB */); } - mFinishes.clear(); } int activeCount() { return mFinishes.size(); } + + int mergeCount() { + return mMerged.size(); + } } private static SurfaceControl createMockSurface(boolean valid) { diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java index 2f38f0ab4509..4e0204b75d2c 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java @@ -25,6 +25,7 @@ import static android.view.WindowManager.TRANSIT_TO_FRONT; import static android.view.WindowManager.TransitionOldType; import static android.window.TransitionInfo.FLAG_IS_WALLPAPER; +import android.os.IBinder; import android.os.RemoteException; import android.util.Log; import android.view.IRemoteAnimationFinishedCallback; @@ -153,7 +154,8 @@ public class RemoteAnimationAdapterCompat { final RemoteAnimationRunnerCompat remoteAnimationAdapter) { return new IRemoteTransition.Stub() { @Override - public void startAnimation(TransitionInfo info, SurfaceControl.Transaction t, + public void startAnimation(IBinder token, TransitionInfo info, + SurfaceControl.Transaction t, IRemoteTransitionFinishedCallback finishCallback) { final RemoteAnimationTargetCompat[] appsCompat = RemoteAnimationTargetCompat.wrap(info, false /* wallpapers */); @@ -256,6 +258,14 @@ public class RemoteAnimationAdapterCompat { appsCompat, wallpapersCompat, nonAppsCompat, animationFinishedCallback); } + + @Override + public void mergeAnimation(IBinder token, TransitionInfo info, + SurfaceControl.Transaction t, IBinder mergeTarget, + IRemoteTransitionFinishedCallback finishCallback) { + // TODO: hook up merge to recents onTaskAppeared if applicable. Until then, ignore + // any incoming merges. + } }; } } diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java index 26ef1455be7d..88d9d68b4458 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java @@ -25,6 +25,7 @@ import static android.view.WindowManager.TRANSIT_TO_FRONT; import android.annotation.NonNull; import android.annotation.Nullable; import android.graphics.Rect; +import android.os.IBinder; import android.os.Parcelable; import android.os.RemoteException; import android.util.Log; @@ -65,7 +66,8 @@ public class RemoteTransitionCompat implements Parcelable { @NonNull Executor executor) { mTransition = new IRemoteTransition.Stub() { @Override - public void startAnimation(TransitionInfo info, SurfaceControl.Transaction t, + public void startAnimation(IBinder transition, TransitionInfo info, + SurfaceControl.Transaction t, IRemoteTransitionFinishedCallback finishedCallback) { final Runnable finishAdapter = () -> { try { @@ -74,7 +76,22 @@ public class RemoteTransitionCompat implements Parcelable { Log.e(TAG, "Failed to call transition finished callback", e); } }; - executor.execute(() -> runner.startAnimation(info, t, finishAdapter)); + executor.execute(() -> runner.startAnimation(transition, info, t, finishAdapter)); + } + + @Override + public void mergeAnimation(IBinder transition, TransitionInfo info, + SurfaceControl.Transaction t, IBinder mergeTarget, + IRemoteTransitionFinishedCallback finishedCallback) { + final Runnable finishAdapter = () -> { + try { + finishedCallback.onTransitionFinished(null /* wct */); + } catch (RemoteException e) { + Log.e(TAG, "Failed to call transition finished callback", e); + } + }; + executor.execute(() -> runner.mergeAnimation(transition, info, t, mergeTarget, + finishAdapter)); } }; } @@ -84,7 +101,8 @@ public class RemoteTransitionCompat implements Parcelable { RecentsAnimationControllerCompat controller) { mTransition = new IRemoteTransition.Stub() { @Override - public void startAnimation(TransitionInfo info, SurfaceControl.Transaction t, + public void startAnimation(IBinder transition, TransitionInfo info, + SurfaceControl.Transaction t, IRemoteTransitionFinishedCallback finishedCallback) { final RemoteAnimationTargetCompat[] apps = RemoteAnimationTargetCompat.wrap(info, false /* wallpapers */); @@ -113,6 +131,13 @@ public class RemoteTransitionCompat implements Parcelable { recents.onAnimationStart(wrapControl, apps, wallpapers, new Rect(0, 0, 0, 0), new Rect()); } + + @Override + public void mergeAnimation(IBinder transition, TransitionInfo info, + SurfaceControl.Transaction t, IBinder mergeTarget, + IRemoteTransitionFinishedCallback finishedCallback) { + // TODO: hook up merge to onTaskAppeared. Until then, just ignore incoming merges. + } }; } diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionRunner.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionRunner.java index 6002bca81c6c..accc456c4209 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionRunner.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionRunner.java @@ -16,6 +16,7 @@ package com.android.systemui.shared.system; +import android.os.IBinder; import android.view.SurfaceControl; import android.window.TransitionInfo; @@ -25,6 +26,17 @@ public interface RemoteTransitionRunner { * Starts a transition animation. Once complete, the implementation should call * `finishCallback`. */ - void startAnimation(TransitionInfo info, SurfaceControl.Transaction t, + void startAnimation(IBinder transition, TransitionInfo info, SurfaceControl.Transaction t, Runnable finishCallback); + + /** + * Attempts to merge a transition into the currently-running animation. If merge is not + * possible/supported, this should do nothing. Otherwise, the implementation should call + * `finishCallback` immediately to indicate that it merged the transition. + * + * @param transition The transition that wants to be merged into the running animation. + * @param mergeTarget The transition to merge into (that this runner is currently animating). + */ + default void mergeAnimation(IBinder transition, TransitionInfo info, + SurfaceControl.Transaction t, IBinder mergeTarget, Runnable finishCallback) { } } diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java index e4ac1f6c5b50..b6109b43851f 100644 --- a/services/core/java/com/android/server/wm/Transition.java +++ b/services/core/java/com/android/server/wm/Transition.java @@ -106,11 +106,9 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe private final BLASTSyncEngine mSyncEngine; private IRemoteTransition mRemoteTransition = null; - /** - * This is a leash to put animating surfaces into flatly without clipping/ordering issues. It - * is a child of all the targets' shared ancestor. - */ - private SurfaceControl mRootLeash = null; + /** Only use for clean-up after binder death! */ + private SurfaceControl.Transaction mStartTransaction = null; + private SurfaceControl.Transaction mFinishTransaction = null; /** * Contains change infos for both participants and all ancestors. We have to track ancestors @@ -229,16 +227,16 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe setReady(true); } - /** The transition has finished animating and is ready to finalize WM state */ - void finishTransition() { - if (mState < STATE_PLAYING) { - throw new IllegalStateException("Can't finish a non-playing transition " + mSyncId); - } + /** + * Build a transaction that "resets" all the re-parenting and layer changes. This is + * intended to be applied at the end of the transition but before the finish callback. This + * needs to be passed/applied in shell because until finish is called, shell owns the surfaces. + * Additionally, this gives shell the ability to better deal with merged transitions. + */ + private void buildFinishTransaction(SurfaceControl.Transaction t, SurfaceControl rootLeash) { final Point tmpPos = new Point(); // usually only size 1 final ArraySet<DisplayContent> displays = new ArraySet<>(); - // Immediately apply all surface reparents, don't wait for pending/sync/etc. - SurfaceControl.Transaction t = mController.mAtm.mWindowManager.mTransactionFactory.get(); for (int i = mTargets.size() - 1; i >= 0; --i) { final WindowContainer target = mTargets.valueAt(i); if (target.getParent() != null) { @@ -247,9 +245,6 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe // Ensure surfaceControls are re-parented back into the hierarchy. t.reparent(targetLeash, origParent); t.setLayer(targetLeash, target.getLastLayer()); - // TODO(shell-transitions): Once all remotables have been moved, see if there is - // a more appropriate place to do the following. This may - // involve passing an SF transaction from shell on finish. target.getRelativePosition(tmpPos); t.setPosition(targetLeash, tmpPos.x, tmpPos.y); t.setCornerRadius(targetLeash, 0); @@ -257,25 +252,26 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe displays.add(target.getDisplayContent()); } } - // Need to update layers on ALL displays (for now) since they were all paused while - // the animation played. + // Need to update layers on involved displays since they were all paused while + // the animation played. This puts the layers back into the correct order. for (int i = displays.size() - 1; i >= 0; --i) { if (displays.valueAt(i) == null) continue; displays.valueAt(i).assignChildLayers(t); } - // Also pro-actively hide going-invisible activity surfaces in same transaction to - // prevent flickers due to reparenting and animation z-order mismatch. - for (int i = mParticipants.size() - 1; i >= 0; --i) { - final ActivityRecord ar = mParticipants.valueAt(i).asActivityRecord(); - if (ar == null || ar.mVisibleRequested || !ar.isVisible()) continue; - t.hide(ar.getSurfaceControl()); + if (rootLeash.isValid()) { + t.reparent(rootLeash, null); } - if (mRootLeash.isValid()) { - t.remove(mRootLeash); + } + + /** + * The transition has finished animating and is ready to finalize WM state. This should not + * be called directly; use {@link TransitionController#finishTransition} instead. + */ + void finishTransition() { + mStartTransaction = mFinishTransaction = null; + if (mState < STATE_PLAYING) { + throw new IllegalStateException("Can't finish a non-playing transition " + mSyncId); } - mRootLeash = null; - t.apply(); - t.close(); // Commit all going-invisible containers for (int i = 0; i < mParticipants.size(); ++i) { @@ -343,29 +339,60 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe // Resolve the animating targets from the participants mTargets = calculateTargets(mParticipants, mChanges); final TransitionInfo info = calculateTransitionInfo(mType, mFlags, mTargets, mChanges); - mRootLeash = info.getRootLeash(); handleNonAppWindowsInTransition(displayId, mType, mFlags); + // Manually show any activities that are visibleRequested. This is needed to properly + // support simultaneous animation queueing/merging. Specifically, if transition A makes + // an activity invisible, it's finishTransaction (which is applied *after* the animation) + // will hide the activity surface. If transition B then makes the activity visible again, + // the normal surfaceplacement logic won't add a show to this start transaction because + // the activity visibility hasn't been committed yet. To deal with this, we have to manually + // show here in the same way that we manually hide in finishTransaction. + for (int i = mParticipants.size() - 1; i >= 0; --i) { + final ActivityRecord ar = mParticipants.valueAt(i).asActivityRecord(); + if (ar == null || !ar.mVisibleRequested) continue; + transaction.show(ar.getSurfaceControl()); + } + + mStartTransaction = transaction; + mFinishTransaction = mController.mAtm.mWindowManager.mTransactionFactory.get(); + buildFinishTransaction(mFinishTransaction, info.getRootLeash()); if (mController.getTransitionPlayer() != null) { try { ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Calling onTransitionReady: %s", info); - mController.getTransitionPlayer().onTransitionReady(this, info, transaction); + mController.getTransitionPlayer().onTransitionReady( + this, info, transaction, mFinishTransaction); } catch (RemoteException e) { // If there's an exception when trying to send the mergedTransaction to the // client, we should finish and apply it here so the transactions aren't lost. - transaction.apply(); - finishTransition(); + cleanUpOnFailure(); } } else { // No player registered, so just finish/apply immediately - transaction.apply(); - finishTransition(); + cleanUpOnFailure(); } mSyncId = -1; } + /** + * If the remote failed for any reason, use this to do any appropriate clean-up. Do not call + * this directly, it's designed to by called by {@link TransitionController} only. + */ + void cleanUpOnFailure() { + // No need to clean-up if this isn't playing yet. + if (mState < STATE_PLAYING) return; + + if (mStartTransaction != null) { + mStartTransaction.apply(); + } + if (mFinishTransaction != null) { + mFinishTransaction.apply(); + } + finishTransition(); + } + private void handleNonAppWindowsInTransition(int displayId, @WindowManager.TransitionType int transit, int flags) { final DisplayContent dc = diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java index 6338f39e2e67..cc63c4922c32 100644 --- a/services/core/java/com/android/server/wm/TransitionController.java +++ b/services/core/java/com/android/server/wm/TransitionController.java @@ -42,17 +42,24 @@ class TransitionController { private static final String TAG = "TransitionController"; private ITransitionPlayer mTransitionPlayer; - private final IBinder.DeathRecipient mTransitionPlayerDeath = () -> mTransitionPlayer = null; final ActivityTaskManagerService mAtm; - /** Currently playing transitions. When finished, records are removed from this list. */ - private final ArrayList<Transition> mPlayingTransitions = new ArrayList<>(); - /** - * The transition currently being constructed (collecting participants). - * TODO(shell-transitions): When simultaneous transitions are supported, merge this with - * mPlayingTransitions. + * Currently playing transitions (in the order they were started). When finished, records are + * removed from this list. */ + private final ArrayList<Transition> mPlayingTransitions = new ArrayList<>(); + + private final IBinder.DeathRecipient mTransitionPlayerDeath = () -> { + // clean-up/finish any playing transitions. + for (int i = 0; i < mPlayingTransitions.size(); ++i) { + mPlayingTransitions.get(i).cleanUpOnFailure(); + } + mPlayingTransitions.clear(); + mTransitionPlayer = null; + }; + + /** The transition currently being constructed (collecting participants). */ private Transition mCollectingTransition = null; TransitionController(ActivityTaskManagerService atm) { 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 2d4e4efd74ea..5a63fbff33d9 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java @@ -1462,7 +1462,8 @@ class WindowTestsBase extends SystemServiceTestsBase { @Override public void onTransitionReady(IBinder transitToken, TransitionInfo transitionInfo, - SurfaceControl.Transaction transaction) throws RemoteException { + SurfaceControl.Transaction transaction, SurfaceControl.Transaction finishT) + throws RemoteException { mLastTransit = Transition.fromBinder(transitToken); mLastReady = transitionInfo; } |