summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/animation/ViewHierarchyAnimator.kt35
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/animation/ViewHierarchyAnimatorTest.kt32
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 */)