Add support for multiple animation tracks
The shell-transition player "Transitions" only supported playing one animation at a time. It supported limited support for concurrent animation via `merging`, but this is for specific/curated situations. Since support via merge is complicated, there weren't many implementors and instead, by default, animations would just jump-to-end to allow the next animation to start immediately (to minimize perceived latency). Unfortunately, if there are TRULY independent transitions, this mechanism is unweildy as it'd require adding support for any incoming transition into any active animation. This CL moves the existing queue/merge mechanism into a "track" and then adds support for multiple tracks to play simultaneously. This way, for transitions which aren't independent, the mechanism doesn't change; however, for truly independent transitions, their corresponding animations can also run independently. The expectation is for WMCore to assign track ids to transitions. Then the player (Transitions.java) can use this information to either play them in parallel or, in the future, do some type of merging on its own. The default is that, all transitions with the same track-id will play in the same track and serialize with eachother -- but otherwise the tracks are independent. There may, however, be some situations where a transition might conflict with more than 1 track. In this case, we just fall-back to a global "SYNC" and basically wait/ flush all the running animations/tracks before starting. Supporting anything fancier is not worth the effort since this situation isn't likely to be very common. This SYNC is actually implemented by just generalizing the existing SLEEP failsafe mechanic. We now just treat an incoming SLEEP the same as SYNC. Bug: 277838915 Bug: 264536014 Test: atest ShellTransitionTests Test: this change, alone, should be a no-op so existing tests too. Change-Id: I97ca21e0917be884cac105a6cb2d2c656f0e4207
This commit is contained in:
@@ -152,8 +152,14 @@ public final class TransitionInfo implements Parcelable {
|
||||
/** The task became the top-most task even if it didn't change visibility. */
|
||||
public static final int FLAG_MOVED_TO_TOP = 1 << 20;
|
||||
|
||||
/**
|
||||
* This transition must be the only transition when it starts (ie. it must wait for all other
|
||||
* transition animations to finish).
|
||||
*/
|
||||
public static final int FLAG_SYNC = 1 << 21;
|
||||
|
||||
/** The first unused bit. This can be used by remotes to attach custom flags to this change. */
|
||||
public static final int FLAG_FIRST_CUSTOM = 1 << 21;
|
||||
public static final int FLAG_FIRST_CUSTOM = 1 << 22;
|
||||
|
||||
/** The change belongs to a window that won't contain activities. */
|
||||
public static final int FLAGS_IS_NON_APP_WINDOW =
|
||||
@@ -183,12 +189,14 @@ public final class TransitionInfo implements Parcelable {
|
||||
FLAG_NO_ANIMATION,
|
||||
FLAG_TASK_LAUNCHING_BEHIND,
|
||||
FLAG_MOVED_TO_TOP,
|
||||
FLAG_SYNC,
|
||||
FLAG_FIRST_CUSTOM
|
||||
})
|
||||
public @interface ChangeFlags {}
|
||||
|
||||
private final @TransitionType int mType;
|
||||
private final @TransitionFlags int mFlags;
|
||||
private @TransitionFlags int mFlags;
|
||||
private int mTrack = 0;
|
||||
private final ArrayList<Change> mChanges = new ArrayList<>();
|
||||
private final ArrayList<Root> mRoots = new ArrayList<>();
|
||||
|
||||
@@ -210,6 +218,7 @@ public final class TransitionInfo implements Parcelable {
|
||||
in.readTypedList(mRoots, Root.CREATOR);
|
||||
mOptions = in.readTypedObject(AnimationOptions.CREATOR);
|
||||
mDebugId = in.readInt();
|
||||
mTrack = in.readInt();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -221,6 +230,7 @@ public final class TransitionInfo implements Parcelable {
|
||||
dest.writeTypedList(mRoots, flags);
|
||||
dest.writeTypedObject(mOptions, flags);
|
||||
dest.writeInt(mDebugId);
|
||||
dest.writeInt(mTrack);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@@ -262,6 +272,10 @@ public final class TransitionInfo implements Parcelable {
|
||||
return mType;
|
||||
}
|
||||
|
||||
public void setFlags(int flags) {
|
||||
mFlags = flags;
|
||||
}
|
||||
|
||||
public int getFlags() {
|
||||
return mFlags;
|
||||
}
|
||||
@@ -356,6 +370,16 @@ public final class TransitionInfo implements Parcelable {
|
||||
return (mFlags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY) != 0;
|
||||
}
|
||||
|
||||
/** Gets which animation track this transition should run on. */
|
||||
public int getTrack() {
|
||||
return mTrack;
|
||||
}
|
||||
|
||||
/** Sets which animation track this transition should run on. */
|
||||
public void setTrack(int track) {
|
||||
mTrack = track;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set an arbitrary "debug" id for this info. This id will not be used for any "real work",
|
||||
* it is just for debugging and logging.
|
||||
@@ -373,7 +397,8 @@ public final class TransitionInfo implements Parcelable {
|
||||
public String toString() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("{id=").append(mDebugId).append(" t=").append(transitTypeToString(mType))
|
||||
.append(" f=0x").append(Integer.toHexString(mFlags)).append(" r=[");
|
||||
.append(" f=0x").append(Integer.toHexString(mFlags)).append(" trk=").append(mTrack)
|
||||
.append(" r=[");
|
||||
for (int i = 0; i < mRoots.size(); ++i) {
|
||||
if (i > 0) {
|
||||
sb.append(',');
|
||||
@@ -461,6 +486,9 @@ public final class TransitionInfo implements Parcelable {
|
||||
if ((flags & FLAG_TASK_LAUNCHING_BEHIND) != 0) {
|
||||
sb.append((sb.length() == 0 ? "" : "|") + "TASK_LAUNCHING_BEHIND");
|
||||
}
|
||||
if ((flags & FLAG_SYNC) != 0) {
|
||||
sb.append((sb.length() == 0 ? "" : "|") + "SYNC");
|
||||
}
|
||||
if ((flags & FLAG_FIRST_CUSTOM) != 0) {
|
||||
sb.append(sb.length() == 0 ? "" : "|").append("FIRST_CUSTOM");
|
||||
}
|
||||
@@ -532,6 +560,7 @@ public final class TransitionInfo implements Parcelable {
|
||||
*/
|
||||
public TransitionInfo localRemoteCopy() {
|
||||
final TransitionInfo out = new TransitionInfo(mType, mFlags);
|
||||
out.mTrack = mTrack;
|
||||
out.mDebugId = mDebugId;
|
||||
for (int i = 0; i < mChanges.size(); ++i) {
|
||||
out.mChanges.add(mChanges.get(i).localRemoteCopy());
|
||||
|
||||
@@ -235,6 +235,7 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler,
|
||||
private TransitionInfo subCopy(@NonNull TransitionInfo info,
|
||||
@WindowManager.TransitionType int newType, boolean withChanges) {
|
||||
final TransitionInfo out = new TransitionInfo(newType, withChanges ? info.getFlags() : 0);
|
||||
out.setTrack(info.getTrack());
|
||||
out.setDebugId(info.getDebugId());
|
||||
if (withChanges) {
|
||||
for (int i = 0; i < info.getChanges().size(); ++i) {
|
||||
|
||||
@@ -90,14 +90,21 @@ import java.util.Arrays;
|
||||
* 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.
|
||||
* The READY and beyond lifecycle is managed per "track". Within a track, all the animations are
|
||||
* serialized as described; however, multiple tracks can play simultaneously. This implies that,
|
||||
* within a track, only one transition can be animating ("active") at a time.
|
||||
*
|
||||
* Once the "active" transition animation is finished, it will be removed from the "active" list
|
||||
* and then the next "ready" transition can play.
|
||||
* While a transition is animating in a track, transitions dispatched to the track 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" list and then the next
|
||||
* "ready" transition can attempt to merge. Once the "active" transition animation is finished,
|
||||
* the next "ready" transition can play.
|
||||
*
|
||||
* Track assignments are expected to be provided by WMCore and this generally tries to maintain
|
||||
* the same assignments. If, however, WMCore decides that a transition conflicts with >1 active
|
||||
* track, it will be marked as SYNC. This means that all currently active tracks must be flushed
|
||||
* before the SYNC transition can play.
|
||||
*/
|
||||
public class Transitions implements RemoteCallable<Transitions> {
|
||||
static final String TAG = "ShellTransitions";
|
||||
@@ -172,12 +179,15 @@ public class Transitions implements RemoteCallable<Transitions> {
|
||||
private float mTransitionAnimationScaleSetting = 1.0f;
|
||||
|
||||
/**
|
||||
* How much time we allow for an animation to finish itself on sleep. If it takes longer, we
|
||||
* How much time we allow for an animation to finish itself on sync. If it takes longer, we
|
||||
* will force-finish it (on this end) which may leave it in a bad state but won't hang the
|
||||
* device. This needs to be pretty small because it is an allowance for each queued animation,
|
||||
* however it can't be too small since there is some potential IPC involved.
|
||||
*/
|
||||
private static final int SLEEP_ALLOWANCE_MS = 120;
|
||||
private static final int SYNC_ALLOWANCE_MS = 120;
|
||||
|
||||
/** For testing only. Disables the force-finish timeout on sync. */
|
||||
private boolean mDisableForceSync = false;
|
||||
|
||||
private static final class ActiveTransition {
|
||||
IBinder mToken;
|
||||
@@ -190,23 +200,45 @@ public class Transitions implements RemoteCallable<Transitions> {
|
||||
/** Ordered list of transitions which have been merged into this one. */
|
||||
private ArrayList<ActiveTransition> mMerged;
|
||||
|
||||
boolean isSync() {
|
||||
return (mInfo.getFlags() & TransitionInfo.FLAG_SYNC) != 0;
|
||||
}
|
||||
|
||||
int getTrack() {
|
||||
return mInfo != null ? mInfo.getTrack() : -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
if (mInfo != null && mInfo.getDebugId() >= 0) {
|
||||
return "(#" + mInfo.getDebugId() + ")" + mToken;
|
||||
return "(#" + mInfo.getDebugId() + ")" + mToken + "@" + getTrack();
|
||||
}
|
||||
return mToken.toString();
|
||||
return mToken.toString() + "@" + getTrack();
|
||||
}
|
||||
}
|
||||
|
||||
private static class Track {
|
||||
/** Keeps track of transitions which are ready to play but still waiting for their turn. */
|
||||
final ArrayList<ActiveTransition> mReadyTransitions = new ArrayList<>();
|
||||
|
||||
/** The currently playing transition in this track. */
|
||||
ActiveTransition mActiveTransition = null;
|
||||
|
||||
boolean isIdle() {
|
||||
return mActiveTransition == null && mReadyTransitions.isEmpty();
|
||||
}
|
||||
}
|
||||
|
||||
/** 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<>();
|
||||
/**
|
||||
* Transitions which are ready to play, but haven't been sent to a track yet because a sync
|
||||
* is ongoing.
|
||||
*/
|
||||
private final ArrayList<ActiveTransition> mReadyDuringSync = new ArrayList<>();
|
||||
|
||||
/** Keeps track of currently playing transitions. For now, there can only be 1 max. */
|
||||
private final ArrayList<ActiveTransition> mActiveTransitions = new ArrayList<>();
|
||||
private final ArrayList<Track> mTracks = new ArrayList<>();
|
||||
|
||||
public Transitions(@NonNull Context context,
|
||||
@NonNull ShellInit shellInit,
|
||||
@@ -374,14 +406,17 @@ public class Transitions implements RemoteCallable<Transitions> {
|
||||
* will be executed when the last active transition is finished.
|
||||
*/
|
||||
public void runOnIdle(Runnable runnable) {
|
||||
if (mActiveTransitions.isEmpty() && mPendingTransitions.isEmpty()
|
||||
&& mReadyTransitions.isEmpty()) {
|
||||
if (isIdle()) {
|
||||
runnable.run();
|
||||
} else {
|
||||
mRunWhenIdleQueue.add(runnable);
|
||||
}
|
||||
}
|
||||
|
||||
void setDisableForceSyncForTest(boolean disable) {
|
||||
mDisableForceSync = disable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up visibility/alpha/transforms to resemble the starting state of an animation.
|
||||
*/
|
||||
@@ -541,6 +576,13 @@ public class Transitions implements RemoteCallable<Transitions> {
|
||||
return true;
|
||||
}
|
||||
|
||||
private Track getOrCreateTrack(int trackId) {
|
||||
while (trackId >= mTracks.size()) {
|
||||
mTracks.add(new Track());
|
||||
}
|
||||
return mTracks.get(trackId);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
void onTransitionReady(@NonNull IBinder transitionToken, @NonNull TransitionInfo info,
|
||||
@NonNull SurfaceControl.Transaction t, @NonNull SurfaceControl.Transaction finishT) {
|
||||
@@ -553,29 +595,58 @@ public class Transitions implements RemoteCallable<Transitions> {
|
||||
+ Arrays.toString(mPendingTransitions.stream().map(
|
||||
activeTransition -> activeTransition.mToken).toArray()));
|
||||
}
|
||||
if (activeIdx > 0) {
|
||||
Log.e(TAG, "Transition became ready out-of-order " + mPendingTransitions.get(activeIdx)
|
||||
+ ". 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;
|
||||
if (activeIdx > 0) {
|
||||
Log.i(TAG, "Transition might be ready out-of-order " + activeIdx + " for " + active
|
||||
+ ". This is ok if it's on a different track.");
|
||||
}
|
||||
if (!mReadyDuringSync.isEmpty()) {
|
||||
mReadyDuringSync.add(active);
|
||||
} else {
|
||||
dispatchReady(active);
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < mObservers.size(); ++i) {
|
||||
mObservers.get(i).onTransitionReady(transitionToken, info, t, finishT);
|
||||
/**
|
||||
* Returns true if dispatching succeeded, otherwise false. Dispatching can fail if it is
|
||||
* blocked by a sync or sleep.
|
||||
*/
|
||||
boolean dispatchReady(ActiveTransition active) {
|
||||
final TransitionInfo info = active.mInfo;
|
||||
|
||||
if (info.getType() == TRANSIT_SLEEP || active.isSync()) {
|
||||
// Adding to *front*! If we are here, it means that it was pulled off the front
|
||||
// so we are just putting it back; or, it is the first one so it doesn't matter.
|
||||
mReadyDuringSync.add(0, active);
|
||||
boolean hadPreceding = false;
|
||||
// Now flush all the tracks.
|
||||
for (int i = 0; i < mTracks.size(); ++i) {
|
||||
final Track tr = mTracks.get(i);
|
||||
if (tr.isIdle()) continue;
|
||||
hadPreceding = true;
|
||||
// Sleep starts a process of forcing all prior transitions to finish immediately
|
||||
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
|
||||
"Start finish-for-sync track %d", i);
|
||||
finishForSync(i, null /* forceFinish */);
|
||||
}
|
||||
if (hadPreceding) {
|
||||
return false;
|
||||
}
|
||||
// Actually able to process the sleep now, so re-remove it from the queue and continue
|
||||
// the normal flow.
|
||||
mReadyDuringSync.remove(active);
|
||||
}
|
||||
|
||||
if (info.getType() == TRANSIT_SLEEP) {
|
||||
if (activeIdx > 0 || !mActiveTransitions.isEmpty() || mReadyTransitions.size() > 1) {
|
||||
// Sleep starts a process of forcing all prior transitions to finish immediately
|
||||
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Start finish-for-sleep");
|
||||
finishForSleep(null /* forceFinish */);
|
||||
return;
|
||||
}
|
||||
final Track track = getOrCreateTrack(info.getTrack());
|
||||
track.mReadyTransitions.add(active);
|
||||
|
||||
for (int i = 0; i < mObservers.size(); ++i) {
|
||||
mObservers.get(i).onTransitionReady(
|
||||
active.mToken, info, active.mStartT, active.mFinishT);
|
||||
}
|
||||
|
||||
if (info.getRootCount() == 0 && !alwaysReportToKeyguard(info)) {
|
||||
@@ -584,7 +655,7 @@ public class Transitions implements RemoteCallable<Transitions> {
|
||||
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "No transition roots in %s so"
|
||||
+ " abort", active);
|
||||
onAbort(active);
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
|
||||
final int changeSize = info.getChanges().size();
|
||||
@@ -614,16 +685,17 @@ public class Transitions implements RemoteCallable<Transitions> {
|
||||
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
|
||||
"Non-visible anim so abort: %s", active);
|
||||
onAbort(active);
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
|
||||
setupStartState(active.mInfo, active.mStartT, active.mFinishT);
|
||||
|
||||
if (mReadyTransitions.size() > 1) {
|
||||
if (track.mReadyTransitions.size() > 1) {
|
||||
// There are already transitions waiting in the queue, so just return.
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
processReadyQueue();
|
||||
processReadyQueue(track);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -643,25 +715,53 @@ public class Transitions implements RemoteCallable<Transitions> {
|
||||
return false;
|
||||
}
|
||||
|
||||
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();
|
||||
private boolean areTracksIdle() {
|
||||
for (int i = 0; i < mTracks.size(); ++i) {
|
||||
if (!mTracks.get(i).isIdle()) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean isAnimating() {
|
||||
return !mReadyDuringSync.isEmpty() || !areTracksIdle();
|
||||
}
|
||||
|
||||
private boolean isIdle() {
|
||||
return mPendingTransitions.isEmpty() && !isAnimating();
|
||||
}
|
||||
|
||||
void processReadyQueue(Track track) {
|
||||
if (track.mReadyTransitions.isEmpty()) {
|
||||
if (track.mActiveTransition == null) {
|
||||
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Track %d became idle",
|
||||
mTracks.indexOf(track));
|
||||
if (areTracksIdle()) {
|
||||
if (!mReadyDuringSync.isEmpty()) {
|
||||
// Dispatch everything unless we hit another sync
|
||||
while (!mReadyDuringSync.isEmpty()) {
|
||||
ActiveTransition next = mReadyDuringSync.remove(0);
|
||||
boolean success = dispatchReady(next);
|
||||
// Hit a sync or sleep, so stop dispatching.
|
||||
if (!success) break;
|
||||
}
|
||||
} else if (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();
|
||||
}
|
||||
}
|
||||
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);
|
||||
final ActiveTransition ready = track.mReadyTransitions.get(0);
|
||||
if (track.mActiveTransition == null) {
|
||||
// The normal case, just play it.
|
||||
track.mReadyTransitions.remove(0);
|
||||
track.mActiveTransition = ready;
|
||||
if (ready.mAborted) {
|
||||
// finish now since there's nothing to animate. Calls back into processReadyQueue
|
||||
onFinish(ready, null, null);
|
||||
@@ -669,11 +769,11 @@ public class Transitions implements RemoteCallable<Transitions> {
|
||||
}
|
||||
playTransition(ready);
|
||||
// Attempt to merge any more queued-up transitions.
|
||||
processReadyQueue();
|
||||
processReadyQueue(track);
|
||||
return;
|
||||
}
|
||||
// An existing animation is playing, so see if we can merge.
|
||||
final ActiveTransition playing = mActiveTransitions.get(0);
|
||||
final ActiveTransition playing = track.mActiveTransition;
|
||||
if (ready.mAborted) {
|
||||
// record as merged since it is no-op. Calls back into processReadyQueue
|
||||
onMerged(playing, ready);
|
||||
@@ -687,18 +787,23 @@ public class Transitions implements RemoteCallable<Transitions> {
|
||||
}
|
||||
|
||||
private void onMerged(@NonNull ActiveTransition playing, @NonNull ActiveTransition merged) {
|
||||
if (playing.getTrack() != merged.getTrack()) {
|
||||
throw new IllegalStateException("Can't merge across tracks: " + merged + " into "
|
||||
+ playing);
|
||||
}
|
||||
final Track track = mTracks.get(playing.getTrack());
|
||||
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Transition was merged: %s into %s",
|
||||
merged, playing);
|
||||
int readyIdx = 0;
|
||||
if (mReadyTransitions.isEmpty() || mReadyTransitions.get(0) != merged) {
|
||||
if (track.mReadyTransitions.isEmpty() || track.mReadyTransitions.get(0) != merged) {
|
||||
Log.e(TAG, "Merged transition out-of-order? " + merged);
|
||||
readyIdx = mReadyTransitions.indexOf(merged);
|
||||
readyIdx = track.mReadyTransitions.indexOf(merged);
|
||||
if (readyIdx < 0) {
|
||||
Log.e(TAG, "Merged a transition that is no-longer queued? " + merged);
|
||||
return;
|
||||
}
|
||||
}
|
||||
mReadyTransitions.remove(readyIdx);
|
||||
track.mReadyTransitions.remove(readyIdx);
|
||||
if (playing.mMerged == null) {
|
||||
playing.mMerged = new ArrayList<>();
|
||||
}
|
||||
@@ -711,7 +816,7 @@ public class Transitions implements RemoteCallable<Transitions> {
|
||||
mObservers.get(i).onTransitionMerged(merged.mToken, playing.mToken);
|
||||
}
|
||||
// See if we should merge another transition.
|
||||
processReadyQueue();
|
||||
processReadyQueue(track);
|
||||
}
|
||||
|
||||
private void playTransition(@NonNull ActiveTransition active) {
|
||||
@@ -780,6 +885,7 @@ public class Transitions implements RemoteCallable<Transitions> {
|
||||
|
||||
/** Aborts a transition. This will still queue it up to maintain order. */
|
||||
private void onAbort(ActiveTransition transition) {
|
||||
final Track track = mTracks.get(transition.getTrack());
|
||||
// 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();
|
||||
@@ -795,11 +901,11 @@ public class Transitions implements RemoteCallable<Transitions> {
|
||||
releaseSurfaces(transition.mInfo);
|
||||
|
||||
// This still went into the queue (to maintain the correct finish ordering).
|
||||
if (mReadyTransitions.size() > 1) {
|
||||
if (track.mReadyTransitions.size() > 1) {
|
||||
// There are already transitions waiting in the queue, so just return.
|
||||
return;
|
||||
}
|
||||
processReadyQueue();
|
||||
processReadyQueue(track);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -814,17 +920,14 @@ public class Transitions implements RemoteCallable<Transitions> {
|
||||
private void onFinish(ActiveTransition active,
|
||||
@Nullable WindowContainerTransaction wct,
|
||||
@Nullable WindowContainerTransactionCallback wctCB) {
|
||||
int activeIdx = mActiveTransitions.indexOf(active);
|
||||
if (activeIdx < 0) {
|
||||
final Track track = mTracks.get(active.getTrack());
|
||||
if (track.mActiveTransition != active) {
|
||||
Log.e(TAG, "Trying to finish a non-running transition. Either remote crashed or "
|
||||
+ " a handler didn't properly deal with a merge. " + active,
|
||||
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);
|
||||
}
|
||||
mActiveTransitions.remove(activeIdx);
|
||||
track.mActiveTransition = null;
|
||||
|
||||
for (int i = 0; i < mObservers.size(); ++i) {
|
||||
mObservers.get(i).onTransitionFinished(active.mToken, active.mAborted);
|
||||
@@ -876,18 +979,20 @@ public class Transitions implements RemoteCallable<Transitions> {
|
||||
}
|
||||
|
||||
// Now that this is done, check the ready queue for more work.
|
||||
processReadyQueue();
|
||||
processReadyQueue(track);
|
||||
}
|
||||
|
||||
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);
|
||||
for (int t = 0; t < mTracks.size(); ++t) {
|
||||
final Track tr = mTracks.get(t);
|
||||
for (int i = 0; i < tr.mReadyTransitions.size(); ++i) {
|
||||
if (tr.mReadyTransitions.get(i).mToken == token) return true;
|
||||
}
|
||||
final ActiveTransition active = tr.mActiveTransition;
|
||||
if (active == null) continue;
|
||||
if (active.mToken == token) return true;
|
||||
if (active.mMerged == null) continue;
|
||||
for (int m = 0; m < active.mMerged.size(); ++m) {
|
||||
@@ -962,7 +1067,7 @@ public class Transitions implements RemoteCallable<Transitions> {
|
||||
*
|
||||
* This works by "merging" the sleep transition into the currently-playing transition (even if
|
||||
* its out-of-order) -- turning SLEEP into a signal. If the playing transition doesn't finish
|
||||
* within `SLEEP_ALLOWANCE_MS` from this merge attempt, this will then finish it directly (and
|
||||
* within `SYNC_ALLOWANCE_MS` from this merge attempt, this will then finish it directly (and
|
||||
* send an abort/consumed message).
|
||||
*
|
||||
* This is then repeated until there are no more pending sleep transitions.
|
||||
@@ -970,48 +1075,53 @@ 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 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.contains(forceFinish)) {
|
||||
Log.e(TAG, "Forcing transition to finish due to sleep timeout: " + forceFinish);
|
||||
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 */);
|
||||
private void finishForSync(int trackIdx, @Nullable ActiveTransition forceFinish) {
|
||||
final Track track = mTracks.get(trackIdx);
|
||||
if (forceFinish != null) {
|
||||
final Track trk = mTracks.get(forceFinish.getTrack());
|
||||
if (trk != track) {
|
||||
Log.e(TAG, "finishForSleep: mismatched Tracks between forceFinish and logic "
|
||||
+ forceFinish.getTrack() + " vs " + trackIdx);
|
||||
}
|
||||
onFinish(forceFinish, null, null);
|
||||
if (trk.mActiveTransition == forceFinish) {
|
||||
Log.e(TAG, "Forcing transition to finish due to sync timeout: " + forceFinish);
|
||||
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);
|
||||
}
|
||||
}
|
||||
if (track.isIdle() || mReadyDuringSync.isEmpty()) {
|
||||
// Done finishing things.
|
||||
return;
|
||||
}
|
||||
final SurfaceControl.Transaction dummyT = new SurfaceControl.Transaction();
|
||||
final TransitionInfo dummyInfo = new TransitionInfo(TRANSIT_SLEEP, 0 /* flags */);
|
||||
while (!mActiveTransitions.isEmpty() && !mSleepHandler.mSleepTransitions.isEmpty()) {
|
||||
final ActiveTransition playing = mActiveTransitions.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 = mReadyTransitions.get(sleepIdx);
|
||||
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Attempt to merge SLEEP %s"
|
||||
+ " into %s", nextSleep, playing);
|
||||
playing.mHandler.mergeAnimation(nextSleep.mToken, dummyInfo, dummyT,
|
||||
playing.mToken, (wct, cb) -> {});
|
||||
} else {
|
||||
Log.e(TAG, "Couldn't find sleep transition in ready list: "
|
||||
+ mSleepHandler.mSleepTransitions.get(0));
|
||||
while (track.mActiveTransition != null && !mReadyDuringSync.isEmpty()) {
|
||||
final ActiveTransition playing = track.mActiveTransition;
|
||||
final ActiveTransition nextSync = mReadyDuringSync.get(0);
|
||||
if (!nextSync.isSync()) {
|
||||
Log.e(TAG, "Somehow blocked on a non-sync transition? " + nextSync);
|
||||
}
|
||||
// Attempt to merge a SLEEP info to signal that the playing transition needs to
|
||||
// fast-forward.
|
||||
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Attempt to merge sync %s"
|
||||
+ " into %s via a SLEEP proxy", nextSync, playing);
|
||||
playing.mHandler.mergeAnimation(nextSync.mToken, dummyInfo, dummyT,
|
||||
playing.mToken, (wct, cb) -> {});
|
||||
// it's possible to complete immediately. If that happens, just repeat the signal
|
||||
// loop until we either finish everything or start playing an animation that isn't
|
||||
// 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), SLEEP_ALLOWANCE_MS);
|
||||
if (track.mActiveTransition == playing) {
|
||||
if (!mDisableForceSync) {
|
||||
// Give it a short amount of time to process it before forcing.
|
||||
mMainExecutor.executeDelayed(() -> finishForSync(trackIdx, playing),
|
||||
SYNC_ALLOWANCE_MS);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,9 +48,8 @@ public class TestShellExecutor implements ShellExecutor {
|
||||
}
|
||||
|
||||
public void flushAll() {
|
||||
for (Runnable r : mRunnables) {
|
||||
r.run();
|
||||
while (!mRunnables.isEmpty()) {
|
||||
mRunnables.remove(0).run();
|
||||
}
|
||||
mRunnables.clear();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,10 +30,12 @@ import static android.view.WindowManager.TRANSIT_CLOSE;
|
||||
import static android.view.WindowManager.TRANSIT_FIRST_CUSTOM;
|
||||
import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY;
|
||||
import static android.view.WindowManager.TRANSIT_OPEN;
|
||||
import static android.view.WindowManager.TRANSIT_SLEEP;
|
||||
import static android.view.WindowManager.TRANSIT_TO_BACK;
|
||||
import static android.view.WindowManager.TRANSIT_TO_FRONT;
|
||||
import static android.window.TransitionInfo.FLAG_DISPLAY_HAS_ALERT_WINDOWS;
|
||||
import static android.window.TransitionInfo.FLAG_IS_DISPLAY;
|
||||
import static android.window.TransitionInfo.FLAG_SYNC;
|
||||
import static android.window.TransitionInfo.FLAG_TRANSLUCENT;
|
||||
|
||||
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
|
||||
@@ -63,6 +65,7 @@ import android.os.IBinder;
|
||||
import android.os.Looper;
|
||||
import android.os.RemoteException;
|
||||
import android.util.ArraySet;
|
||||
import android.util.Pair;
|
||||
import android.view.Surface;
|
||||
import android.view.SurfaceControl;
|
||||
import android.view.WindowManager;
|
||||
@@ -587,7 +590,8 @@ public class ShellTransitionTests extends ShellTestCase {
|
||||
requestStartTransition(transitions, transitTokenNotReady);
|
||||
|
||||
mDefaultHandler.setSimulateMerge(true);
|
||||
mDefaultHandler.mFinishes.get(0).onTransitionFinished(null /* wct */, null /* wctCB */);
|
||||
mDefaultHandler.mFinishes.get(0).second.onTransitionFinished(
|
||||
null /* wct */, null /* wctCB */);
|
||||
|
||||
// Make sure that the non-ready transition is not merged.
|
||||
assertEquals(0, mDefaultHandler.mergeCount());
|
||||
@@ -1059,6 +1063,223 @@ public class ShellTransitionTests extends ShellTestCase {
|
||||
assertEquals(1, mDefaultHandler.activeCount());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMultipleTracks() {
|
||||
Transitions transitions = createTestTransitions();
|
||||
transitions.replaceDefaultHandlerForTest(mDefaultHandler);
|
||||
TestTransitionHandler alwaysMergeHandler = new TestTransitionHandler();
|
||||
alwaysMergeHandler.setSimulateMerge(true);
|
||||
|
||||
final boolean[] becameIdle = new boolean[]{false};
|
||||
|
||||
final WindowContainerTransaction emptyWCT = new WindowContainerTransaction();
|
||||
final SurfaceControl.Transaction mockSCT = mock(SurfaceControl.Transaction.class);
|
||||
|
||||
// Make this always merge so we can ensure that it does NOT get a merge-attempt for a
|
||||
// different track.
|
||||
IBinder transitA = transitions.startTransition(TRANSIT_OPEN, emptyWCT, alwaysMergeHandler);
|
||||
// start tracking idle
|
||||
transitions.runOnIdle(() -> becameIdle[0] = true);
|
||||
|
||||
IBinder transitB = transitions.startTransition(TRANSIT_OPEN, emptyWCT, mDefaultHandler);
|
||||
IBinder transitC = transitions.startTransition(TRANSIT_CLOSE, emptyWCT, mDefaultHandler);
|
||||
|
||||
TransitionInfo infoA = new TransitionInfoBuilder(TRANSIT_OPEN)
|
||||
.addChange(TRANSIT_OPEN).build();
|
||||
infoA.setTrack(0);
|
||||
TransitionInfo infoB = new TransitionInfoBuilder(TRANSIT_OPEN)
|
||||
.addChange(TRANSIT_OPEN).build();
|
||||
infoB.setTrack(1);
|
||||
TransitionInfo infoC = new TransitionInfoBuilder(TRANSIT_CLOSE)
|
||||
.addChange(TRANSIT_OPEN).addChange(TRANSIT_CLOSE).build();
|
||||
infoC.setTrack(1);
|
||||
|
||||
transitions.onTransitionReady(transitA, infoA, mockSCT, mockSCT);
|
||||
assertEquals(1, alwaysMergeHandler.activeCount());
|
||||
transitions.onTransitionReady(transitB, infoB, mockSCT, mockSCT);
|
||||
// should now be running in parallel
|
||||
assertEquals(1, mDefaultHandler.activeCount());
|
||||
assertEquals(1, alwaysMergeHandler.activeCount());
|
||||
// make sure we didn't try to merge into a different track.
|
||||
assertEquals(0, alwaysMergeHandler.mergeCount());
|
||||
|
||||
// This should be queued-up since it is on track 1 (same as B)
|
||||
transitions.onTransitionReady(transitC, infoC, mockSCT, mockSCT);
|
||||
assertEquals(1, mDefaultHandler.activeCount());
|
||||
assertEquals(1, alwaysMergeHandler.activeCount());
|
||||
|
||||
// Now finish B and make sure C starts
|
||||
mDefaultHandler.finishOne();
|
||||
mMainExecutor.flushAll();
|
||||
|
||||
// Now C and A running in parallel
|
||||
assertEquals(1, mDefaultHandler.activeCount());
|
||||
assertEquals(1, alwaysMergeHandler.activeCount());
|
||||
assertEquals(0, alwaysMergeHandler.mergeCount());
|
||||
|
||||
// Finish A
|
||||
alwaysMergeHandler.finishOne();
|
||||
mMainExecutor.flushAll();
|
||||
|
||||
// C still running
|
||||
assertEquals(0, alwaysMergeHandler.activeCount());
|
||||
assertEquals(1, mDefaultHandler.activeCount());
|
||||
assertFalse(becameIdle[0]);
|
||||
|
||||
mDefaultHandler.finishOne();
|
||||
mMainExecutor.flushAll();
|
||||
|
||||
assertEquals(0, mDefaultHandler.activeCount());
|
||||
assertTrue(becameIdle[0]);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSyncMultipleTracks() {
|
||||
Transitions transitions = createTestTransitions();
|
||||
transitions.replaceDefaultHandlerForTest(mDefaultHandler);
|
||||
TestTransitionHandler secondHandler = new TestTransitionHandler();
|
||||
|
||||
// Disable the forced early-sync-finish so that we can test the ordering mechanics.
|
||||
transitions.setDisableForceSyncForTest(true);
|
||||
mDefaultHandler.mFinishOnSync = false;
|
||||
secondHandler.mFinishOnSync = false;
|
||||
|
||||
final WindowContainerTransaction emptyWCT = new WindowContainerTransaction();
|
||||
final SurfaceControl.Transaction mockSCT = mock(SurfaceControl.Transaction.class);
|
||||
|
||||
// Make this always merge so we can ensure that it does NOT get a merge-attempt for a
|
||||
// different track.
|
||||
IBinder transitA = transitions.startTransition(TRANSIT_OPEN, emptyWCT, mDefaultHandler);
|
||||
IBinder transitB = transitions.startTransition(TRANSIT_OPEN, emptyWCT, secondHandler);
|
||||
IBinder transitC = transitions.startTransition(TRANSIT_CLOSE, emptyWCT, secondHandler);
|
||||
IBinder transitSync = transitions.startTransition(TRANSIT_CLOSE, emptyWCT, mDefaultHandler);
|
||||
IBinder transitD = transitions.startTransition(TRANSIT_OPEN, emptyWCT, secondHandler);
|
||||
IBinder transitE = transitions.startTransition(TRANSIT_OPEN, emptyWCT, mDefaultHandler);
|
||||
|
||||
TransitionInfo infoA = new TransitionInfoBuilder(TRANSIT_OPEN)
|
||||
.addChange(TRANSIT_OPEN).build();
|
||||
infoA.setTrack(0);
|
||||
TransitionInfo infoB = new TransitionInfoBuilder(TRANSIT_OPEN)
|
||||
.addChange(TRANSIT_OPEN).build();
|
||||
infoB.setTrack(1);
|
||||
TransitionInfo infoC = new TransitionInfoBuilder(TRANSIT_CLOSE)
|
||||
.addChange(TRANSIT_OPEN).addChange(TRANSIT_CLOSE).build();
|
||||
infoC.setTrack(1);
|
||||
TransitionInfo infoSync = new TransitionInfoBuilder(TRANSIT_CLOSE)
|
||||
.addChange(TRANSIT_OPEN).addChange(TRANSIT_CLOSE).build();
|
||||
infoSync.setTrack(0);
|
||||
infoSync.setFlags(FLAG_SYNC);
|
||||
TransitionInfo infoD = new TransitionInfoBuilder(TRANSIT_OPEN)
|
||||
.addChange(TRANSIT_OPEN).build();
|
||||
infoD.setTrack(1);
|
||||
TransitionInfo infoE = new TransitionInfoBuilder(TRANSIT_OPEN)
|
||||
.addChange(TRANSIT_OPEN).build();
|
||||
infoE.setTrack(0);
|
||||
|
||||
// Start A B and C where A is track 0, B and C are track 1 (C should be queued)
|
||||
transitions.onTransitionReady(transitA, infoA, mockSCT, mockSCT);
|
||||
transitions.onTransitionReady(transitB, infoB, mockSCT, mockSCT);
|
||||
transitions.onTransitionReady(transitC, infoC, mockSCT, mockSCT);
|
||||
// should now be running in parallel (with one queued)
|
||||
assertEquals(1, mDefaultHandler.activeCount());
|
||||
assertEquals(1, secondHandler.activeCount());
|
||||
|
||||
// Make the sync ready and the following (D, E) ready.
|
||||
transitions.onTransitionReady(transitSync, infoSync, mockSCT, mockSCT);
|
||||
transitions.onTransitionReady(transitD, infoD, mockSCT, mockSCT);
|
||||
transitions.onTransitionReady(transitE, infoE, mockSCT, mockSCT);
|
||||
|
||||
// nothing should have happened yet since the sync is queued and blocking everything.
|
||||
assertEquals(1, mDefaultHandler.activeCount());
|
||||
assertEquals(1, secondHandler.activeCount());
|
||||
|
||||
// Finish A (which is track 0 like the sync).
|
||||
mDefaultHandler.finishOne();
|
||||
mMainExecutor.flushAll();
|
||||
|
||||
// Even though the sync is on track 0 and track 0 became idle, it should NOT be started yet
|
||||
// because it must wait for everything. Additionally, D/E shouldn't start yet either.
|
||||
assertEquals(0, mDefaultHandler.activeCount());
|
||||
assertEquals(1, secondHandler.activeCount());
|
||||
|
||||
// Now finish B and C -- this should then allow the sync to start and D to run (in parallel)
|
||||
secondHandler.finishOne();
|
||||
secondHandler.finishOne();
|
||||
mMainExecutor.flushAll();
|
||||
|
||||
// Now the sync and D (on track 1) should be running
|
||||
assertEquals(1, mDefaultHandler.activeCount());
|
||||
assertEquals(1, secondHandler.activeCount());
|
||||
|
||||
// finish the sync. track 0 still has E
|
||||
mDefaultHandler.finishOne();
|
||||
mMainExecutor.flushAll();
|
||||
assertEquals(1, mDefaultHandler.activeCount());
|
||||
|
||||
mDefaultHandler.finishOne();
|
||||
secondHandler.finishOne();
|
||||
mMainExecutor.flushAll();
|
||||
|
||||
assertEquals(0, mDefaultHandler.activeCount());
|
||||
assertEquals(0, secondHandler.activeCount());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testForceSyncTracks() {
|
||||
Transitions transitions = createTestTransitions();
|
||||
transitions.replaceDefaultHandlerForTest(mDefaultHandler);
|
||||
TestTransitionHandler secondHandler = new TestTransitionHandler();
|
||||
|
||||
final WindowContainerTransaction emptyWCT = new WindowContainerTransaction();
|
||||
final SurfaceControl.Transaction mockSCT = mock(SurfaceControl.Transaction.class);
|
||||
|
||||
// Make this always merge so we can ensure that it does NOT get a merge-attempt for a
|
||||
// different track.
|
||||
IBinder transitA = transitions.startTransition(TRANSIT_OPEN, emptyWCT, mDefaultHandler);
|
||||
IBinder transitB = transitions.startTransition(TRANSIT_OPEN, emptyWCT, mDefaultHandler);
|
||||
IBinder transitC = transitions.startTransition(TRANSIT_CLOSE, emptyWCT, secondHandler);
|
||||
IBinder transitD = transitions.startTransition(TRANSIT_OPEN, emptyWCT, secondHandler);
|
||||
IBinder transitSync = transitions.startTransition(TRANSIT_CLOSE, emptyWCT, mDefaultHandler);
|
||||
|
||||
TransitionInfo infoA = new TransitionInfoBuilder(TRANSIT_OPEN)
|
||||
.addChange(TRANSIT_OPEN).build();
|
||||
infoA.setTrack(0);
|
||||
TransitionInfo infoB = new TransitionInfoBuilder(TRANSIT_OPEN)
|
||||
.addChange(TRANSIT_OPEN).build();
|
||||
infoB.setTrack(0);
|
||||
TransitionInfo infoC = new TransitionInfoBuilder(TRANSIT_CLOSE)
|
||||
.addChange(TRANSIT_OPEN).addChange(TRANSIT_CLOSE).build();
|
||||
infoC.setTrack(1);
|
||||
TransitionInfo infoD = new TransitionInfoBuilder(TRANSIT_OPEN)
|
||||
.addChange(TRANSIT_OPEN).build();
|
||||
infoD.setTrack(1);
|
||||
TransitionInfo infoSync = new TransitionInfoBuilder(TRANSIT_CLOSE)
|
||||
.addChange(TRANSIT_OPEN).addChange(TRANSIT_CLOSE).build();
|
||||
infoSync.setTrack(0);
|
||||
infoSync.setFlags(FLAG_SYNC);
|
||||
|
||||
transitions.onTransitionReady(transitA, infoA, mockSCT, mockSCT);
|
||||
transitions.onTransitionReady(transitB, infoB, mockSCT, mockSCT);
|
||||
transitions.onTransitionReady(transitC, infoC, mockSCT, mockSCT);
|
||||
transitions.onTransitionReady(transitD, infoD, mockSCT, mockSCT);
|
||||
// should now be running in parallel (with one queued in each)
|
||||
assertEquals(1, mDefaultHandler.activeCount());
|
||||
assertEquals(1, secondHandler.activeCount());
|
||||
|
||||
// Make the sync ready.
|
||||
transitions.onTransitionReady(transitSync, infoSync, mockSCT, mockSCT);
|
||||
mMainExecutor.flushAll();
|
||||
|
||||
// Everything should be forced-finish now except the sync
|
||||
assertEquals(1, mDefaultHandler.activeCount());
|
||||
assertEquals(0, secondHandler.activeCount());
|
||||
|
||||
mDefaultHandler.finishOne();
|
||||
mMainExecutor.flushAll();
|
||||
|
||||
assertEquals(0, mDefaultHandler.activeCount());
|
||||
}
|
||||
|
||||
class ChangeBuilder {
|
||||
final TransitionInfo.Change mChange;
|
||||
|
||||
@@ -1097,9 +1318,11 @@ public class ShellTransitionTests extends ShellTestCase {
|
||||
}
|
||||
|
||||
class TestTransitionHandler implements Transitions.TransitionHandler {
|
||||
ArrayList<Transitions.TransitionFinishCallback> mFinishes = new ArrayList<>();
|
||||
ArrayList<Pair<IBinder, Transitions.TransitionFinishCallback>> mFinishes =
|
||||
new ArrayList<>();
|
||||
final ArrayList<IBinder> mMerged = new ArrayList<>();
|
||||
boolean mSimulateMerge = false;
|
||||
boolean mFinishOnSync = true;
|
||||
final ArraySet<IBinder> mShouldMerge = new ArraySet<>();
|
||||
|
||||
@Override
|
||||
@@ -1107,7 +1330,7 @@ public class ShellTransitionTests extends ShellTestCase {
|
||||
@NonNull SurfaceControl.Transaction startTransaction,
|
||||
@NonNull SurfaceControl.Transaction finishTransaction,
|
||||
@NonNull Transitions.TransitionFinishCallback finishCallback) {
|
||||
mFinishes.add(finishCallback);
|
||||
mFinishes.add(new Pair<>(transition, finishCallback));
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1115,6 +1338,13 @@ 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 (mFinishOnSync && info.getType() == TRANSIT_SLEEP) {
|
||||
for (int i = 0; i < mFinishes.size(); ++i) {
|
||||
if (mFinishes.get(i).first != mergeTarget) continue;
|
||||
mFinishes.remove(i).second.onTransitionFinished(null, null);
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (!(mSimulateMerge || mShouldMerge.contains(transition))) return;
|
||||
mMerged.add(transition);
|
||||
finishCallback.onTransitionFinished(null /* wct */, null /* wctCB */);
|
||||
@@ -1136,18 +1366,19 @@ public class ShellTransitionTests extends ShellTestCase {
|
||||
}
|
||||
|
||||
void finishAll() {
|
||||
final ArrayList<Transitions.TransitionFinishCallback> finishes = mFinishes;
|
||||
final ArrayList<Pair<IBinder, Transitions.TransitionFinishCallback>> finishes =
|
||||
mFinishes;
|
||||
mFinishes = new ArrayList<>();
|
||||
for (int i = finishes.size() - 1; i >= 0; --i) {
|
||||
finishes.get(i).onTransitionFinished(null /* wct */, null /* wctCB */);
|
||||
finishes.get(i).second.onTransitionFinished(null /* wct */, null /* wctCB */);
|
||||
}
|
||||
mShouldMerge.clear();
|
||||
}
|
||||
|
||||
void finishOne() {
|
||||
Transitions.TransitionFinishCallback fin = mFinishes.remove(0);
|
||||
Pair<IBinder, Transitions.TransitionFinishCallback> fin = mFinishes.remove(0);
|
||||
mMerged.clear();
|
||||
fin.onTransitionFinished(null /* wct */, null /* wctCB */);
|
||||
fin.second.onTransitionFinished(null /* wct */, null /* wctCB */);
|
||||
}
|
||||
|
||||
int activeCount() {
|
||||
|
||||
Reference in New Issue
Block a user