Merge "Refactor transition player to fix merge ordering" into udc-dev

This commit is contained in:
Evan Rosky
2023-03-08 17:49:56 +00:00
committed by Android (Google) Code Review
2 changed files with 306 additions and 198 deletions

View File

@@ -79,7 +79,24 @@ import com.android.wm.shell.sysui.ShellInit;
import java.util.ArrayList;
import java.util.Arrays;
/** Plays transition animations */
/**
* Plays transition animations. Within this player, each transition has a lifecycle.
* 1. When a transition is directly started or requested, it is added to "pending" state.
* 2. Once WMCore applies the transition and notifies, the transition moves to "ready" state.
* 3. When a transition starts animating, it is moved to the "active" state.
*
* Basically: --start--> PENDING --onTransitionReady--> READY --play--> ACTIVE --finish--> |
* --merge--> MERGED --^
*
* At the moment, only one transition can be animating at a time. While a transition is animating,
* transitions will be queued in the "ready" state for their turn. At the same time, whenever a
* transition makes it to the head of the "ready" queue, it will attempt to merge to with the
* "active" transition. If the merge succeeds, it will be moved to the "active" transition's
* "merged" and then the next "ready" transition can attempt to merge.
*
* Once the "active" transition animation is finished, it will be removed from the "active" list
* and then the next "ready" transition can play.
*/
public class Transitions implements RemoteCallable<Transitions> {
static final String TAG = "ShellTransitions";
@@ -150,14 +167,22 @@ public class Transitions implements RemoteCallable<Transitions> {
private static final class ActiveTransition {
IBinder mToken;
TransitionHandler mHandler;
boolean mMerged;
boolean mAborted;
TransitionInfo mInfo;
SurfaceControl.Transaction mStartT;
SurfaceControl.Transaction mFinishT;
/** Ordered list of transitions which have been merged into this one. */
private ArrayList<ActiveTransition> mMerged;
}
/** Keeps track of currently playing transitions in the order of receipt. */
/** Keeps track of transitions which have been started, but aren't ready yet. */
private final ArrayList<ActiveTransition> mPendingTransitions = new ArrayList<>();
/** Keeps track of transitions which are ready to play but still waiting for their turn. */
private final ArrayList<ActiveTransition> mReadyTransitions = new ArrayList<>();
/** Keeps track of currently playing transitions. For now, there can only be 1 max. */
private final ArrayList<ActiveTransition> mActiveTransitions = new ArrayList<>();
public Transitions(@NonNull Context context,
@@ -322,7 +347,8 @@ public class Transitions implements RemoteCallable<Transitions> {
* will be executed when the last active transition is finished.
*/
public void runOnIdle(Runnable runnable) {
if (mActiveTransitions.isEmpty()) {
if (mActiveTransitions.isEmpty() && mPendingTransitions.isEmpty()
&& mReadyTransitions.isEmpty()) {
runnable.run();
} else {
mRunWhenIdleQueue.add(runnable);
@@ -441,9 +467,9 @@ 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;
private static int findByToken(ArrayList<ActiveTransition> list, IBinder token) {
for (int i = list.size() - 1; i >= 0; --i) {
if (list.get(i).mToken == token) return i;
}
return -1;
}
@@ -481,14 +507,24 @@ public class Transitions implements RemoteCallable<Transitions> {
@NonNull SurfaceControl.Transaction t, @NonNull SurfaceControl.Transaction finishT) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "onTransitionReady %s: %s",
transitionToken, info);
final int activeIdx = findActiveTransition(transitionToken);
final int activeIdx = findByToken(mPendingTransitions, transitionToken);
if (activeIdx < 0) {
throw new IllegalStateException("Got transitionReady for non-active transition "
throw new IllegalStateException("Got transitionReady for non-pending transition "
+ transitionToken + ". expecting one of "
+ Arrays.toString(mActiveTransitions.stream().map(
+ Arrays.toString(mPendingTransitions.stream().map(
activeTransition -> activeTransition.mToken).toArray()));
}
final ActiveTransition active = mActiveTransitions.get(activeIdx);
if (activeIdx > 0) {
Log.e(TAG, "Transition became ready out-of-order " + transitionToken + ". Expected"
+ " order: " + Arrays.toString(mPendingTransitions.stream().map(
activeTransition -> activeTransition.mToken).toArray()));
}
// Move from pending to ready
final ActiveTransition active = mPendingTransitions.remove(activeIdx);
mReadyTransitions.add(active);
active.mInfo = info;
active.mStartT = t;
active.mFinishT = finishT;
for (int i = 0; i < mObservers.size(); ++i) {
mObservers.get(i).onTransitionReady(transitionToken, info, t, finishT);
@@ -496,9 +532,6 @@ public class Transitions implements RemoteCallable<Transitions> {
if (info.getType() == TRANSIT_SLEEP) {
if (activeIdx > 0) {
active.mInfo = info;
active.mStartT = t;
active.mFinishT = finishT;
if (!info.getRootLeash().isValid()) {
// Shell has some debug settings which makes calling binders with invalid
// surfaces crash, so replace it with a "real" one.
@@ -518,9 +551,7 @@ public class Transitions implements RemoteCallable<Transitions> {
// housekeeping and return.
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Invalid root leash (%s): %s",
transitionToken, info);
t.apply();
finishT.apply();
onAbort(transitionToken);
onAbort(active);
return;
}
@@ -546,40 +577,90 @@ public class Transitions implements RemoteCallable<Transitions> {
// changes are underneath another change.
|| ((info.getType() == TRANSIT_TO_BACK || info.getType() == TRANSIT_TO_FRONT)
&& allOccluded)) {
t.apply();
finishT.apply();
// Treat this as an abort since we are bypassing any merge logic and effectively
// finishing immediately.
onAbort(transitionToken);
releaseSurfaces(info);
onAbort(active);
return;
}
active.mInfo = info;
active.mStartT = t;
active.mFinishT = finishT;
setupStartState(active.mInfo, active.mStartT, active.mFinishT);
if (activeIdx > 0) {
// This is now playing at the same time as an existing animation, so try merging it.
attemptMergeTransition(mActiveTransitions.get(0), active);
if (mReadyTransitions.size() > 1) {
// There are already transitions waiting in the queue, so just return.
return;
}
// The normal case, just play it.
playTransition(active);
processReadyQueue();
}
/**
* Attempt to merge by delegating the transition start to the handler of the currently
* playing transition.
*/
void attemptMergeTransition(@NonNull ActiveTransition playing,
@NonNull ActiveTransition merging) {
void processReadyQueue() {
if (mReadyTransitions.isEmpty()) {
// Check if idle.
if (mActiveTransitions.isEmpty() && mPendingTransitions.isEmpty()) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "All active transition "
+ "animations finished");
// Run all runnables from the run-when-idle queue.
for (int i = 0; i < mRunWhenIdleQueue.size(); i++) {
mRunWhenIdleQueue.get(i).run();
}
mRunWhenIdleQueue.clear();
}
return;
}
final ActiveTransition ready = mReadyTransitions.get(0);
if (mActiveTransitions.isEmpty()) {
// The normal case, just play it (currently we only support 1 active transition).
mReadyTransitions.remove(0);
mActiveTransitions.add(ready);
if (ready.mAborted) {
// finish now since there's nothing to animate. Calls back into processReadyQueue
onFinish(ready, null, null);
return;
}
playTransition(ready);
// Attempt to merge any more queued-up transitions.
processReadyQueue();
return;
}
// An existing animation is playing, so see if we can merge.
final ActiveTransition playing = mActiveTransitions.get(0);
if (ready.mAborted) {
// record as merged since it is no-op. Calls back into processReadyQueue
onMerged(playing, ready);
return;
}
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));
+ " in case they can be merged", ready.mToken, playing.mToken);
playing.mHandler.mergeAnimation(ready.mToken, ready.mInfo, ready.mStartT,
playing.mToken, (wct, cb) -> onMerged(playing, ready));
}
private void onMerged(@NonNull ActiveTransition playing, @NonNull ActiveTransition merged) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Transition was merged %s",
merged.mToken);
int readyIdx = 0;
if (mReadyTransitions.isEmpty() || mReadyTransitions.get(0) != merged) {
Log.e(TAG, "Merged transition out-of-order?");
readyIdx = mReadyTransitions.indexOf(merged);
if (readyIdx < 0) {
Log.e(TAG, "Merged a transition that is no-longer queued?");
return;
}
}
mReadyTransitions.remove(readyIdx);
if (playing.mMerged == null) {
playing.mMerged = new ArrayList<>();
}
playing.mMerged.add(merged);
// if it was aborted, then onConsumed has already been reported.
if (merged.mHandler != null && !merged.mAborted) {
merged.mHandler.onTransitionConsumed(merged.mToken, false /* abort */, merged.mFinishT);
}
for (int i = 0; i < mObservers.size(); ++i) {
mObservers.get(i).onTransitionMerged(merged.mToken, playing.mToken);
}
// See if we should merge another transition.
processReadyQueue();
}
private void playTransition(@NonNull ActiveTransition active) {
@@ -594,7 +675,7 @@ public class Transitions implements RemoteCallable<Transitions> {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " try firstHandler %s",
active.mHandler);
boolean consumed = active.mHandler.startAnimation(active.mToken, active.mInfo,
active.mStartT, active.mFinishT, (wct, cb) -> onFinish(active.mToken, wct, cb));
active.mStartT, active.mFinishT, (wct, cb) -> onFinish(active, wct, cb));
if (consumed) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " animated by firstHandler");
return;
@@ -602,7 +683,7 @@ public class Transitions implements RemoteCallable<Transitions> {
}
// Otherwise give every other handler a chance
active.mHandler = dispatchTransition(active.mToken, active.mInfo, active.mStartT,
active.mFinishT, (wct, cb) -> onFinish(active.mToken, wct, cb), active.mHandler);
active.mFinishT, (wct, cb) -> onFinish(active, wct, cb), active.mHandler);
}
/**
@@ -645,15 +726,28 @@ public class Transitions implements RemoteCallable<Transitions> {
return null;
}
/** Special version of finish just for dealing with no-op/invalid transitions. */
private void onAbort(IBinder transition) {
onFinish(transition, null /* wct */, null /* wctCB */, true /* abort */);
}
/** Aborts a transition. This will still queue it up to maintain order. */
private void onAbort(ActiveTransition transition) {
// apply immediately since they may be "parallel" operations: We currently we use abort for
// thing which are independent to other transitions (like starting-window transfer).
transition.mStartT.apply();
transition.mFinishT.apply();
transition.mAborted = true;
private void onFinish(IBinder transition,
@Nullable WindowContainerTransaction wct,
@Nullable WindowContainerTransactionCallback wctCB) {
onFinish(transition, wct, wctCB, false /* abort */);
if (transition.mHandler != null) {
// Notifies to clean-up the aborted transition.
transition.mHandler.onTransitionConsumed(
transition.mToken, true /* aborted */, null /* finishTransaction */);
}
releaseSurfaces(transition.mInfo);
// This still went into the queue (to maintain the correct finish ordering).
if (mReadyTransitions.size() > 1) {
// There are already transitions waiting in the queue, so just return.
return;
}
processReadyQueue();
}
/**
@@ -665,167 +759,97 @@ public class Transitions implements RemoteCallable<Transitions> {
info.releaseAnimSurfaces();
}
private void onFinish(IBinder transition,
private void onFinish(ActiveTransition active,
@Nullable WindowContainerTransaction wct,
@Nullable WindowContainerTransactionCallback wctCB,
boolean abort) {
int activeIdx = findActiveTransition(transition);
@Nullable WindowContainerTransactionCallback wctCB) {
int activeIdx = mActiveTransitions.indexOf(active);
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 (abort=%b:"
+ " %s", abort, transition);
final ActiveTransition active = mActiveTransitions.get(activeIdx);
active.mMerged = true;
active.mAborted = abort;
if (active.mHandler != null) {
active.mHandler.onTransitionConsumed(
active.mToken, abort, abort ? null : active.mFinishT);
}
for (int i = 0; i < mObservers.size(); ++i) {
mObservers.get(i).onTransitionMerged(
active.mToken, mActiveTransitions.get(0).mToken);
}
+ " a handler didn't properly deal with a merge. " + active.mToken,
new RuntimeException());
return;
} else if (activeIdx != 0) {
// Relevant right now since we only allow 1 active transition at a time.
Log.e(TAG, "Finishing a transition out of order. " + active.mToken);
}
final ActiveTransition active = mActiveTransitions.get(activeIdx);
active.mAborted = abort;
if (active.mAborted && active.mHandler != null) {
// Notifies to clean-up the aborted transition.
active.mHandler.onTransitionConsumed(
transition, true /* aborted */, null /* finishTransaction */);
}
mActiveTransitions.remove(activeIdx);
for (int i = 0; i < mObservers.size(); ++i) {
mObservers.get(i).onTransitionFinished(active.mToken, active.mAborted);
}
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
"Transition animation finished (abort=%b), notifying core %s", abort, transition);
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Transition animation finished "
+ "(aborted=%b), notifying core %s", active.mAborted, active.mToken);
if (active.mStartT != null) {
// Applied by now, so clear immediately to remove any references. Do not set to null
// yet, though, since nullness is used later to disambiguate malformed transitions.
active.mStartT.clear();
}
// Merge all relevant transactions together
// Merge all associated transactions together
SurfaceControl.Transaction fullFinish = active.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.
if (toMerge.mStartT != null) {
if (fullFinish == null) {
fullFinish = toMerge.mStartT;
} else {
fullFinish.merge(toMerge.mStartT);
if (active.mMerged != null) {
for (int iM = 0; iM < active.mMerged.size(); ++iM) {
final ActiveTransition toMerge = active.mMerged.get(iM);
// Include start. It will be a no-op if it was already applied. Otherwise, we need
// it to maintain consistent state.
if (toMerge.mStartT != null) {
if (fullFinish == null) {
fullFinish = toMerge.mStartT;
} else {
fullFinish.merge(toMerge.mStartT);
}
}
}
if (toMerge.mFinishT != null) {
if (fullFinish == null) {
fullFinish = toMerge.mFinishT;
} else {
fullFinish.merge(toMerge.mFinishT);
if (toMerge.mFinishT != null) {
if (fullFinish == null) {
fullFinish = toMerge.mFinishT;
} else {
fullFinish.merge(toMerge.mFinishT);
}
}
}
}
if (fullFinish != null) {
fullFinish.apply();
}
// Now perform all the finishes.
// Now perform all the finish callbacks (starting with the playing one and then all the
// transitions merged into it).
releaseSurfaces(active.mInfo);
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 */);
releaseSurfaces(merged.mInfo);
}
// sift through aborted transitions
while (mActiveTransitions.size() > activeIdx
&& mActiveTransitions.get(activeIdx).mAborted) {
ActiveTransition aborted = mActiveTransitions.remove(activeIdx);
// Notifies to clean-up the aborted transition.
if (aborted.mHandler != null) {
aborted.mHandler.onTransitionConsumed(
transition, true /* aborted */, null /* finishTransaction */);
mOrganizer.finishTransition(active.mToken, wct, wctCB);
if (active.mMerged != null) {
for (int iM = 0; iM < active.mMerged.size(); ++iM) {
ActiveTransition merged = active.mMerged.get(iM);
mOrganizer.finishTransition(merged.mToken, null /* wct */, null /* wctCB */);
releaseSurfaces(merged.mInfo);
}
mOrganizer.finishTransition(aborted.mToken, null /* wct */, null /* wctCB */);
for (int i = 0; i < mObservers.size(); ++i) {
mObservers.get(i).onTransitionFinished(aborted.mToken, true);
}
releaseSurfaces(aborted.mInfo);
}
if (mActiveTransitions.size() <= activeIdx) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "All active transition animations "
+ "finished");
// Run all runnables from the run-when-idle queue.
for (int i = 0; i < mRunWhenIdleQueue.size(); i++) {
mRunWhenIdleQueue.get(i).run();
}
mRunWhenIdleQueue.clear();
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;
active.mMerged.clear();
}
// 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.mAborted) {
// transition was aborted, so we can skip for now (still leave it in the list
// so that it gets cleaned-up in the right order).
++mergeIdx;
continue;
}
if (mergeCandidate.mInfo == null) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Transition merge candidate"
+ " %s is not ready yet", mergeCandidate.mToken);
// The later transition should not be merged if the prior one is not ready.
return;
}
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;
// Now that this is done, check the ready queue for more work.
processReadyQueue();
}
private boolean isTransitionKnown(IBinder token) {
for (int i = 0; i < mPendingTransitions.size(); ++i) {
if (mPendingTransitions.get(i).mToken == token) return true;
}
for (int i = 0; i < mReadyTransitions.size(); ++i) {
if (mReadyTransitions.get(i).mToken == token) return true;
}
for (int i = 0; i < mActiveTransitions.size(); ++i) {
final ActiveTransition active = mActiveTransitions.get(i);
if (active.mToken == token) return true;
if (active.mMerged == null) continue;
for (int m = 0; m < active.mMerged.size(); ++m) {
if (active.mMerged.get(m).mToken == token) return true;
}
}
return false;
}
void requestStartTransition(@NonNull IBinder transitionToken,
@Nullable TransitionRequestInfo request) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Transition requested: %s %s",
transitionToken, request);
if (findActiveTransition(transitionToken) >= 0) {
if (isTransitionKnown(transitionToken)) {
throw new RuntimeException("Transition already started " + transitionToken);
}
final ActiveTransition active = new ActiveTransition();
@@ -858,15 +882,13 @@ public class Transitions implements RemoteCallable<Transitions> {
}
mOrganizer.startTransition(transitionToken, wct != null && wct.isEmpty() ? null : wct);
active.mToken = transitionToken;
int insertIdx = 0;
for (; insertIdx < mActiveTransitions.size(); ++insertIdx) {
if (mActiveTransitions.get(insertIdx).mInfo == null) {
// A `startNewTransition` was sent to WMCore, but wasn't acknowledged before WMCore
// made this request, so insert this request beforehand to keep order in sync.
break;
}
}
mActiveTransitions.add(insertIdx, active);
// Currently, WMCore only does one transition at a time. If it makes a requestStart, it
// is already collecting that transition on core-side, so it will be the next one to
// become ready. There may already be pending transitions added as part of direct
// `startNewTransition` but if we have a request now, it means WM created the request
// transition before it acknowledged any of the pending `startNew` transitions. So, insert
// it at the front.
mPendingTransitions.add(0, active);
}
/** Start a new transition directly. */
@@ -875,7 +897,7 @@ public class Transitions implements RemoteCallable<Transitions> {
final ActiveTransition active = new ActiveTransition();
active.mHandler = handler;
active.mToken = mOrganizer.startNewTransition(type, wct);
mActiveTransitions.add(active);
mPendingTransitions.add(active);
return active.mToken;
}
@@ -894,27 +916,38 @@ public class Transitions implements RemoteCallable<Transitions> {
* @param forceFinish When non-null, this is the transition that we last sent the SLEEP merge
* signal to -- so it will be force-finished if it's still running.
*/
private void finishForSleep(@Nullable IBinder forceFinish) {
if (mActiveTransitions.isEmpty() || mSleepHandler.mSleepTransitions.isEmpty()) {
private void finishForSleep(@Nullable ActiveTransition forceFinish) {
if ((mActiveTransitions.isEmpty() && mReadyTransitions.isEmpty())
|| mSleepHandler.mSleepTransitions.isEmpty()) {
// Done finishing things.
// Prevent any weird leaks... shouldn't happen though.
mSleepHandler.mSleepTransitions.clear();
return;
}
if (forceFinish != null && mActiveTransitions.get(0).mToken == forceFinish) {
if (forceFinish != null && mActiveTransitions.contains(forceFinish)) {
Log.e(TAG, "Forcing transition to finish due to sleep timeout: "
+ mActiveTransitions.get(0).mToken);
onFinish(mActiveTransitions.get(0).mToken, null, null, true);
+ forceFinish.mToken);
forceFinish.mAborted = true;
// Last notify of it being consumed. Note: mHandler should never be null,
// but check just to be safe.
if (forceFinish.mHandler != null) {
forceFinish.mHandler.onTransitionConsumed(
forceFinish.mToken, true /* aborted */, null /* finishTransaction */);
}
onFinish(forceFinish, null, null);
}
final SurfaceControl.Transaction dummyT = new SurfaceControl.Transaction();
while (!mActiveTransitions.isEmpty() && !mSleepHandler.mSleepTransitions.isEmpty()) {
final ActiveTransition playing = mActiveTransitions.get(0);
int sleepIdx = findActiveTransition(mSleepHandler.mSleepTransitions.get(0));
int sleepIdx = findByToken(mReadyTransitions, mSleepHandler.mSleepTransitions.get(0));
if (sleepIdx >= 0) {
// Try to signal that we are sleeping by attempting to merge the sleep transition
// into the playing one.
final ActiveTransition nextSleep = mActiveTransitions.get(sleepIdx);
final ActiveTransition nextSleep = mReadyTransitions.get(sleepIdx);
playing.mHandler.mergeAnimation(nextSleep.mToken, nextSleep.mInfo, dummyT,
playing.mToken, (wct, cb) -> {});
} else {
Log.e(TAG, "Couldn't find sleep transition in active list: "
Log.e(TAG, "Couldn't find sleep transition in ready list: "
+ mSleepHandler.mSleepTransitions.get(0));
}
// it's possible to complete immediately. If that happens, just repeat the signal
@@ -922,8 +955,7 @@ public class Transitions implements RemoteCallable<Transitions> {
// finishing immediately.
if (!mActiveTransitions.isEmpty() && mActiveTransitions.get(0) == playing) {
// Give it a (very) short amount of time to process it before forcing.
mMainExecutor.executeDelayed(
() -> finishForSleep(playing.mToken), SLEEP_ALLOWANCE_MS);
mMainExecutor.executeDelayed(() -> finishForSleep(playing), SLEEP_ALLOWANCE_MS);
break;
}
}

View File

@@ -62,6 +62,7 @@ import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.RemoteException;
import android.util.ArraySet;
import android.view.Surface;
import android.view.SurfaceControl;
import android.view.WindowManager;
@@ -99,6 +100,7 @@ import org.junit.runner.RunWith;
import org.mockito.InOrder;
import java.util.ArrayList;
import java.util.function.Function;
/**
* Tests for the shell transitions.
@@ -590,6 +592,68 @@ public class ShellTransitionTests extends ShellTestCase {
assertEquals(0, mDefaultHandler.mergeCount());
}
@Test
public void testInterleavedMerging() {
Transitions transitions = createTestTransitions();
transitions.replaceDefaultHandlerForTest(mDefaultHandler);
Function<Boolean, IBinder> startATransition = (doMerge) -> {
IBinder token = new Binder();
if (doMerge) {
mDefaultHandler.setShouldMerge(token);
}
transitions.requestStartTransition(token,
new TransitionRequestInfo(TRANSIT_OPEN, null /* trigger */, null /* remote */));
TransitionInfo info = new TransitionInfoBuilder(TRANSIT_OPEN)
.addChange(TRANSIT_OPEN).addChange(TRANSIT_CLOSE).build();
transitions.onTransitionReady(token, info, mock(SurfaceControl.Transaction.class),
mock(SurfaceControl.Transaction.class));
return token;
};
IBinder transitToken1 = startATransition.apply(false);
// merge first one
IBinder transitToken2 = startATransition.apply(true);
assertEquals(1, mDefaultHandler.activeCount());
assertEquals(1, mDefaultHandler.mergeCount());
// don't merge next one
IBinder transitToken3 = startATransition.apply(false);
// make sure nothing happened (since it wasn't merged)
assertEquals(1, mDefaultHandler.activeCount());
assertEquals(1, mDefaultHandler.mergeCount());
// make a mergable
IBinder transitToken4 = startATransition.apply(true);
// make sure nothing happened since there is a non-mergable pending.
assertEquals(1, mDefaultHandler.activeCount());
assertEquals(1, mDefaultHandler.mergeCount());
// Queue up another mergable
IBinder transitToken5 = startATransition.apply(true);
// Queue up a non-mergable
IBinder transitToken6 = startATransition.apply(false);
// Our active now looks like: [playing, merged]
// and ready queue: [non-mergable, mergable, mergable, non-mergable]
// finish the playing one
mDefaultHandler.finishOne();
mMainExecutor.flushAll();
// Now we should have the non-mergable playing now with 2 merged:
// active: [playing, merged, merged] queue: [non-mergable]
assertEquals(1, mDefaultHandler.activeCount());
assertEquals(2, mDefaultHandler.mergeCount());
mDefaultHandler.finishOne();
mMainExecutor.flushAll();
assertEquals(1, mDefaultHandler.activeCount());
assertEquals(0, mDefaultHandler.mergeCount());
mDefaultHandler.finishOne();
mMainExecutor.flushAll();
}
@Test
public void testTransitionOrderMatchesCore() {
Transitions transitions = createTestTransitions();
@@ -1016,6 +1080,7 @@ public class ShellTransitionTests extends ShellTestCase {
ArrayList<Transitions.TransitionFinishCallback> mFinishes = new ArrayList<>();
final ArrayList<IBinder> mMerged = new ArrayList<>();
boolean mSimulateMerge = false;
final ArraySet<IBinder> mShouldMerge = new ArraySet<>();
@Override
public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
@@ -1030,7 +1095,7 @@ public class ShellTransitionTests extends ShellTestCase {
public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
@NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget,
@NonNull Transitions.TransitionFinishCallback finishCallback) {
if (!mSimulateMerge) return;
if (!(mSimulateMerge || mShouldMerge.contains(transition))) return;
mMerged.add(transition);
finishCallback.onTransitionFinished(null /* wct */, null /* wctCB */);
}
@@ -1046,12 +1111,23 @@ public class ShellTransitionTests extends ShellTestCase {
mSimulateMerge = sim;
}
void setShouldMerge(IBinder toMerge) {
mShouldMerge.add(toMerge);
}
void finishAll() {
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 */);
}
mShouldMerge.clear();
}
void finishOne() {
Transitions.TransitionFinishCallback fin = mFinishes.remove(0);
mMerged.clear();
fin.onTransitionFinished(null /* wct */, null /* wctCB */);
}
int activeCount() {