diff options
| -rw-r--r-- | packages/SystemUI/animation/src/com/android/systemui/animation/ViewHierarchyAnimator.kt | 35 | ||||
| -rw-r--r-- | packages/SystemUI/tests/src/com/android/systemui/animation/ViewHierarchyAnimatorTest.kt | 32 |
2 files changed, 51 insertions, 16 deletions
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/ViewHierarchyAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/ViewHierarchyAnimator.kt index d15b8c169535..74ec7f0ede66 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/ViewHierarchyAnimator.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ViewHierarchyAnimator.kt @@ -156,17 +156,7 @@ class ViewHierarchyAnimator { * Any animations already in progress continue until their natural conclusion. */ fun stopAnimating(rootView: View) { - val listener = rootView.getTag(R.id.tag_layout_listener) - if (listener != null && listener is View.OnLayoutChangeListener) { - rootView.setTag(R.id.tag_layout_listener, null /* tag */) - rootView.removeOnLayoutChangeListener(listener) - } - - if (rootView is ViewGroup) { - for (i in 0 until rootView.childCount) { - stopAnimating(rootView.getChildAt(i)) - } - } + recursivelyRemoveListener(rootView) } /** @@ -462,6 +452,20 @@ class ViewHierarchyAnimator { } } + private fun recursivelyRemoveListener(view: View) { + val listener = view.getTag(R.id.tag_layout_listener) + if (listener != null && listener is View.OnLayoutChangeListener) { + view.setTag(R.id.tag_layout_listener, null /* tag */) + view.removeOnLayoutChangeListener(listener) + } + + if (view is ViewGroup) { + for (i in 0 until view.childCount) { + recursivelyRemoveListener(view.getChildAt(i)) + } + } + } + private fun getBound(view: View, bound: Bound): Int? { return view.getTag(bound.overrideTag) as? Int } @@ -513,11 +517,10 @@ class ViewHierarchyAnimator { // When an animation is cancelled, a new one might be taking over. We shouldn't // unregister the listener yet. if (ephemeral && !cancelled) { - val listener = view.getTag(R.id.tag_layout_listener) - if (listener != null && listener is View.OnLayoutChangeListener) { - view.setTag(R.id.tag_layout_listener, null /* tag */) - view.removeOnLayoutChangeListener(listener) - } + // The duration is the same for the whole hierarchy, so it's safe to remove + // the listener recursively. We do this because some descendant views might + // not change bounds, and therefore not animate and leak the listener. + recursivelyRemoveListener(view) } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/ViewHierarchyAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/ViewHierarchyAnimatorTest.kt index 98d57a3c5da8..1c84ee97dc71 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/animation/ViewHierarchyAnimatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/animation/ViewHierarchyAnimatorTest.kt @@ -522,6 +522,38 @@ class ViewHierarchyAnimatorTest : SysuiTestCase() { } @Test + fun cleansUpListenersCorrectly() { + val firstChild = View(mContext) + firstChild.layoutParams = LinearLayout.LayoutParams(50 /* width */, 100 /* height */) + rootView.addView(firstChild) + val secondChild = View(mContext) + secondChild.layoutParams = LinearLayout.LayoutParams(50 /* width */, 100 /* height */) + rootView.addView(secondChild) + rootView.measure( + View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.EXACTLY), + View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.EXACTLY) + ) + rootView.layout(0 /* l */, 0 /* t */, 100 /* r */, 100 /* b */) + + val success = ViewHierarchyAnimator.animateNextUpdate(rootView) + // Change all bounds. + rootView.measure( + View.MeasureSpec.makeMeasureSpec(150, View.MeasureSpec.EXACTLY), + View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.EXACTLY) + ) + rootView.layout(0 /* l */, 0 /* t */, 150 /* r */, 100 /* b */) + + assertTrue(success) + assertNotNull(rootView.getTag(R.id.tag_layout_listener)) + assertNotNull(firstChild.getTag(R.id.tag_layout_listener)) + assertNotNull(secondChild.getTag(R.id.tag_layout_listener)) + endAnimation(rootView) + assertNull(rootView.getTag(R.id.tag_layout_listener)) + assertNull(firstChild.getTag(R.id.tag_layout_listener)) + assertNull(secondChild.getTag(R.id.tag_layout_listener)) + } + + @Test fun doesNotAnimateInvisibleViews() { rootView.layout(10 /* l */, 10 /* t */, 50 /* r */, 50 /* b */) |