Merge "Add support for multiple animation tracks" into udc-dev
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.
|
||||
*/
|
||||
@@ -542,6 +577,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) {
|
||||
@@ -554,29 +596,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)) {
|
||||
@@ -585,7 +656,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();
|
||||
@@ -615,16 +686,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;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -644,25 +716,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);
|
||||
@@ -670,11 +770,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);
|
||||
@@ -688,18 +788,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<>();
|
||||
}
|
||||
@@ -712,7 +817,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) {
|
||||
@@ -781,6 +886,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();
|
||||
@@ -796,11 +902,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);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -815,17 +921,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);
|
||||
@@ -877,18 +980,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) {
|
||||
@@ -963,7 +1068,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.
|
||||
@@ -971,48 +1076,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