diff --git a/services/core/java/com/android/server/wm/AnimatingAppWindowTokenRegistry.java b/services/core/java/com/android/server/wm/AnimatingAppWindowTokenRegistry.java index 416469bd3be13..9c00d1abafea5 100644 --- a/services/core/java/com/android/server/wm/AnimatingAppWindowTokenRegistry.java +++ b/services/core/java/com/android/server/wm/AnimatingAppWindowTokenRegistry.java @@ -37,6 +37,8 @@ class AnimatingAppWindowTokenRegistry { private ArrayList mTmpRunnableList = new ArrayList<>(); + private boolean mEndingDeferredFinish; + /** * Notifies that an {@link AppWindowToken} has started animating. */ @@ -50,6 +52,11 @@ class AnimatingAppWindowTokenRegistry { void notifyFinished(AppWindowToken token) { mAnimatingTokens.remove(token); mFinishedTokens.remove(token); + + // If we were the last token, make sure the end all deferred finishes. + if (mAnimatingTokens.isEmpty()) { + endDeferringFinished(); + } } /** @@ -78,16 +85,28 @@ class AnimatingAppWindowTokenRegistry { } private void endDeferringFinished() { - // Copy it into a separate temp list to avoid modifying the collection while iterating as - // calling the callback may call back into notifyFinished. - for (int i = mFinishedTokens.size() - 1; i >= 0; i--) { - mTmpRunnableList.add(mFinishedTokens.valueAt(i)); + + // Don't start recursing. Running the finished listener invokes notifyFinished, which may + // invoked us again. + if (mEndingDeferredFinish) { + return; } - mFinishedTokens.clear(); - for (int i = mTmpRunnableList.size() - 1; i >= 0; i--) { - mTmpRunnableList.get(i).run(); + try { + mEndingDeferredFinish = true; + + // Copy it into a separate temp list to avoid modifying the collection while iterating + // as calling the callback may call back into notifyFinished. + for (int i = mFinishedTokens.size() - 1; i >= 0; i--) { + mTmpRunnableList.add(mFinishedTokens.valueAt(i)); + } + mFinishedTokens.clear(); + for (int i = mTmpRunnableList.size() - 1; i >= 0; i--) { + mTmpRunnableList.get(i).run(); + } + mTmpRunnableList.clear(); + } finally { + mEndingDeferredFinish = false; } - mTmpRunnableList.clear(); } void dump(PrintWriter pw, String header, String prefix) { diff --git a/services/tests/servicestests/src/com/android/server/wm/AnimatingAppWindowTokenRegistryTest.java b/services/tests/servicestests/src/com/android/server/wm/AnimatingAppWindowTokenRegistryTest.java index 8b78f10b0b5b7..164c80b2427ab 100644 --- a/services/tests/servicestests/src/com/android/server/wm/AnimatingAppWindowTokenRegistryTest.java +++ b/services/tests/servicestests/src/com/android/server/wm/AnimatingAppWindowTokenRegistryTest.java @@ -88,4 +88,25 @@ public class AnimatingAppWindowTokenRegistryTest extends WindowTestsBase { verify(mMockEndDeferFinishCallback1).run(); verifyZeroInteractions(mMockEndDeferFinishCallback2); } + + @Test + public void testContainerRemoved() throws Exception { + final AppWindowToken window1 = createAppWindowToken(mDisplayContent, + WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD); + final AppWindowToken window2 = createAppWindow(window1.getTask(), ACTIVITY_TYPE_STANDARD, + "window2").mAppToken; + final AnimatingAppWindowTokenRegistry registry = + window1.getStack().getAnimatingAppWindowTokenRegistry(); + + window1.startAnimation(window1.getPendingTransaction(), mAdapter, false /* hidden */); + window2.startAnimation(window1.getPendingTransaction(), mAdapter, false /* hidden */); + assertTrue(window1.isSelfAnimating()); + assertTrue(window2.isSelfAnimating()); + + // Make sure that first animation finish is deferred, and removing the second window stops + // finishes all pending deferred finishings. + registry.notifyAboutToFinish(window1, mMockEndDeferFinishCallback1); + window2.setParent(null); + verify(mMockEndDeferFinishCallback1).run(); + } }