Fix missing Notification collapse animation on LS
When we start to expand a Notification on the LS but we abort it
halfway, the Notification should animate back to the collapsed
state. This animation was missing, because we tried to access the
"ExpandableView#actualHeight" property via reflection.
Test: create a makepush build and observe the animation on the LS
Test: atest DragDownHelperTest
Test: atest PulseExpansionHandlerTest
Fixes: 237084760
Change-Id: I3798dec1ab016d89b61e87a3192199ea16181472
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
index 74d8f1b..d3343df 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
@@ -2,7 +2,6 @@
import android.animation.Animator
import android.animation.AnimatorListenerAdapter
-import android.animation.ObjectAnimator
import android.animation.ValueAnimator
import android.content.Context
import android.content.res.Configuration
@@ -880,15 +879,22 @@
child.actualHeight = (child.collapsedHeight + rubberband).toInt()
}
- private fun cancelChildExpansion(child: ExpandableView) {
+ @VisibleForTesting
+ fun cancelChildExpansion(
+ child: ExpandableView,
+ animationDuration: Long = SPRING_BACK_ANIMATION_LENGTH_MS
+ ) {
if (child.actualHeight == child.collapsedHeight) {
expandCallback.setUserLockedChild(child, false)
return
}
- val anim = ObjectAnimator.ofInt(child, "actualHeight",
- child.actualHeight, child.collapsedHeight)
+ val anim = ValueAnimator.ofInt(child.actualHeight, child.collapsedHeight)
anim.interpolator = Interpolators.FAST_OUT_SLOW_IN
- anim.duration = SPRING_BACK_ANIMATION_LENGTH_MS
+ anim.duration = animationDuration
+ anim.addUpdateListener { animation: ValueAnimator ->
+ // don't use reflection, because the `actualHeight` field may be obfuscated
+ child.actualHeight = animation.animatedValue as Int
+ }
anim.addListener(object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator) {
expandCallback.setUserLockedChild(child, false)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt b/packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt
index 6302ef1..bbff046 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt
@@ -18,7 +18,7 @@
import android.animation.Animator
import android.animation.AnimatorListenerAdapter
-import android.animation.ObjectAnimator
+import android.animation.ValueAnimator
import android.content.Context
import android.content.res.Configuration
import android.os.PowerManager
@@ -28,6 +28,7 @@
import android.view.MotionEvent
import android.view.VelocityTracker
import android.view.ViewConfiguration
+import androidx.annotation.VisibleForTesting
import com.android.systemui.Dumpable
import com.android.systemui.Gefingerpoken
import com.android.systemui.R
@@ -276,15 +277,22 @@
}
}
- private fun reset(child: ExpandableView) {
+ @VisibleForTesting
+ fun reset(
+ child: ExpandableView,
+ animationDuration: Long = SPRING_BACK_ANIMATION_LENGTH_MS.toLong()
+ ) {
if (child.actualHeight == child.collapsedHeight) {
setUserLocked(child, false)
return
}
- val anim = ObjectAnimator.ofInt(child, "actualHeight",
- child.actualHeight, child.collapsedHeight)
+ val anim = ValueAnimator.ofInt(child.actualHeight, child.collapsedHeight)
anim.interpolator = Interpolators.FAST_OUT_SLOW_IN
- anim.duration = SPRING_BACK_ANIMATION_LENGTH_MS.toLong()
+ anim.duration = animationDuration
+ anim.addUpdateListener { animation: ValueAnimator ->
+ // don't use reflection, because the `actualHeight` field may be obfuscated
+ child.actualHeight = animation.animatedValue as Int
+ }
anim.addListener(object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator) {
setUserLocked(child, false)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/DragDownHelperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/DragDownHelperTest.kt
new file mode 100644
index 0000000..d925d0a
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/DragDownHelperTest.kt
@@ -0,0 +1,83 @@
+/*
+ * Copyright (c) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.statusbar
+
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import androidx.test.filters.SmallTest
+import com.android.systemui.ExpandHelper
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.classifier.FalsingCollector
+import com.android.systemui.plugins.FalsingManager
+import com.android.systemui.statusbar.notification.row.ExpandableView
+import com.android.systemui.util.mockito.mock
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.atLeast
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when` as whenever
+
+@SmallTest
+@TestableLooper.RunWithLooper
+@RunWith(AndroidTestingRunner::class)
+class DragDownHelperTest : SysuiTestCase() {
+
+ private lateinit var dragDownHelper: DragDownHelper
+
+ private val collapsedHeight = 300
+ private val falsingManager: FalsingManager = mock()
+ private val falsingCollector: FalsingCollector = mock()
+ private val dragDownloadCallback: LockscreenShadeTransitionController = mock()
+ private val expandableView: ExpandableView = mock()
+ private val expandCallback: ExpandHelper.Callback = mock()
+
+ @Before
+ fun setUp() {
+ whenever(expandableView.collapsedHeight).thenReturn(collapsedHeight)
+
+ dragDownHelper = DragDownHelper(
+ falsingManager,
+ falsingCollector,
+ dragDownloadCallback,
+ mContext
+ ).also {
+ it.expandCallback = expandCallback
+ }
+ }
+
+ @Test
+ fun cancelChildExpansion_updateHeight() {
+ whenever(expandableView.actualHeight).thenReturn(500)
+
+ dragDownHelper.cancelChildExpansion(expandableView, animationDuration = 0)
+
+ verify(expandableView, atLeast(1)).actualHeight = collapsedHeight
+ }
+
+ @Test
+ fun cancelChildExpansion_dontUpdateHeight() {
+ whenever(expandableView.actualHeight).thenReturn(collapsedHeight)
+
+ dragDownHelper.cancelChildExpansion(expandableView, animationDuration = 0)
+
+ verify(expandableView, never()).actualHeight = anyInt()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/PulseExpansionHandlerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/PulseExpansionHandlerTest.kt
new file mode 100644
index 0000000..44cbe51
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/PulseExpansionHandlerTest.kt
@@ -0,0 +1,100 @@
+/*
+ * Copyright (c) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.statusbar
+
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.classifier.FalsingCollector
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.plugins.FalsingManager
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator
+import com.android.systemui.statusbar.notification.row.ExpandableView
+import com.android.systemui.statusbar.notification.stack.NotificationRoundnessManager
+import com.android.systemui.statusbar.phone.HeadsUpManagerPhone
+import com.android.systemui.statusbar.phone.KeyguardBypassController
+import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.util.mockito.mock
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.atLeast
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when` as whenever
+
+@SmallTest
+@TestableLooper.RunWithLooper
+@RunWith(AndroidTestingRunner::class)
+class PulseExpansionHandlerTest : SysuiTestCase() {
+
+ private lateinit var pulseExpansionHandler: PulseExpansionHandler
+
+ private val collapsedHeight = 300
+ private val wakeUpCoordinator: NotificationWakeUpCoordinator = mock()
+ private val bypassController: KeyguardBypassController = mock()
+ private val headsUpManager: HeadsUpManagerPhone = mock()
+ private val roundnessManager: NotificationRoundnessManager = mock()
+ private val configurationController: ConfigurationController = mock()
+ private val statusBarStateController: StatusBarStateController = mock()
+ private val falsingManager: FalsingManager = mock()
+ private val lockscreenShadeTransitionController: LockscreenShadeTransitionController = mock()
+ private val falsingCollector: FalsingCollector = mock()
+ private val dumpManager: DumpManager = mock()
+ private val expandableView: ExpandableView = mock()
+
+ @Before
+ fun setUp() {
+ whenever(expandableView.collapsedHeight).thenReturn(collapsedHeight)
+
+ pulseExpansionHandler = PulseExpansionHandler(
+ mContext,
+ wakeUpCoordinator,
+ bypassController,
+ headsUpManager,
+ roundnessManager,
+ configurationController,
+ statusBarStateController,
+ falsingManager,
+ lockscreenShadeTransitionController,
+ falsingCollector,
+ dumpManager
+ )
+ }
+
+ @Test
+ fun resetChild_updateHeight() {
+ whenever(expandableView.actualHeight).thenReturn(500)
+
+ pulseExpansionHandler.reset(expandableView, animationDuration = 0)
+
+ verify(expandableView, atLeast(1)).actualHeight = collapsedHeight
+ }
+
+ @Test
+ fun resetChild_dontUpdateHeight() {
+ whenever(expandableView.actualHeight).thenReturn(collapsedHeight)
+
+ pulseExpansionHandler.reset(expandableView, animationDuration = 0)
+
+ verify(expandableView, never()).actualHeight = anyInt()
+ }
+}