Prevent animation update callbacks after it has finished

Remove the update listener once the animation has ended or has been
canceled to prevent further updates from trying to animate a surface
that has already been released.
This scenario can happend when mergeAnimation() manually runs end() on
animations that already finished but had not been removed yet. This may
happen because the removal of the animation is posted from the shell
anim thread to the shell main thread, so mergeAnimation() may win a race
and schedule another end() before the first end() removes the
animations.

Bug: 252872225
Test: boot, no shell.anim crash
Change-Id: I442f1152fba97c30ce425b08f1aa65bb94fb3bb4
This commit is contained in:
Jorge Gil
2022-10-18 19:46:45 +00:00
parent 4cca001428
commit 6dd00dbf5a

View File

@@ -740,12 +740,13 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
// Animation length is already expected to be scaled. // Animation length is already expected to be scaled.
va.overrideDurationScale(1.0f); va.overrideDurationScale(1.0f);
va.setDuration(anim.computeDurationHint()); va.setDuration(anim.computeDurationHint());
va.addUpdateListener(animation -> { final ValueAnimator.AnimatorUpdateListener updateListener = animation -> {
final long currentPlayTime = Math.min(va.getDuration(), va.getCurrentPlayTime()); final long currentPlayTime = Math.min(va.getDuration(), va.getCurrentPlayTime());
applyTransformation(currentPlayTime, transaction, leash, anim, transformation, matrix, applyTransformation(currentPlayTime, transaction, leash, anim, transformation, matrix,
position, cornerRadius, clipRect); position, cornerRadius, clipRect);
}); };
va.addUpdateListener(updateListener);
final Runnable finisher = () -> { final Runnable finisher = () -> {
applyTransformation(va.getDuration(), transaction, leash, anim, transformation, matrix, applyTransformation(va.getDuration(), transaction, leash, anim, transformation, matrix,
@@ -758,20 +759,30 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
}); });
}; };
va.addListener(new AnimatorListenerAdapter() { va.addListener(new AnimatorListenerAdapter() {
// It is possible for the end/cancel to be called more than once, which may cause
// issues if the animating surface has already been released. Track the finished
// state here to skip duplicate callbacks. See b/252872225.
private boolean mFinished = false; private boolean mFinished = false;
@Override @Override
public void onAnimationEnd(Animator animation) { public void onAnimationEnd(Animator animation) {
if (mFinished) return; onFinish();
mFinished = true;
finisher.run();
} }
@Override @Override
public void onAnimationCancel(Animator animation) { public void onAnimationCancel(Animator animation) {
onFinish();
}
private void onFinish() {
if (mFinished) return; if (mFinished) return;
mFinished = true; mFinished = true;
finisher.run(); finisher.run();
// The update listener can continue to be called after the animation has ended if
// end() is called manually again before the finisher removes the animation.
// Remove it manually here to prevent animating a released surface.
// See b/252872225.
va.removeUpdateListener(updateListener);
} }
}); });
animations.add(va); animations.add(va);