summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/java/android/window/IRemoteTransition.aidl18
-rw-r--r--core/java/android/window/ITransitionPlayer.aidl5
-rw-r--r--libs/WindowManager/Shell/Android.bp2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java25
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java66
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java232
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java11
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java153
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java12
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java31
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionRunner.java14
-rw-r--r--services/core/java/com/android/server/wm/Transition.java95
-rw-r--r--services/core/java/com/android/server/wm/TransitionController.java21
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java3
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;
}