Merge "Add a 5 second timeout to launch animation touch blocking" into udc-qpr-dev
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
index 802ed80..798f2d5 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
@@ -107,7 +107,14 @@
     private boolean mTouchActive;
     private boolean mTouchCancelled;
     private MotionEvent mDownEvent;
+    // TODO rename to mLaunchAnimationRunning
     private boolean mExpandAnimationRunning;
+    /**
+     *  When mExpandAnimationRunning is true and the touch dispatcher receives a down even after
+     *  uptime exceeds this, the dispatcher will stop blocking touches for the launch animation,
+     *  which has presumabely not completed due to an error.
+     */
+    private long mLaunchAnimationTimeout;
     private NotificationStackScrollLayout mStackScrollLayout;
     private PhoneStatusBarViewController mStatusBarViewController;
     private final CentralSurfaces mService;
@@ -280,7 +287,12 @@
                     return logDownDispatch(ev, "touch cancelled", false);
                 }
                 if (mExpandAnimationRunning) {
-                    return logDownDispatch(ev, "expand animation running", false);
+                    if (isDown && mClock.uptimeMillis() > mLaunchAnimationTimeout) {
+                        mShadeLogger.d("NSWVC: launch animation timed out");
+                        setExpandAnimationRunning(false);
+                    } else {
+                        return logDownDispatch(ev, "expand animation running", false);
+                    }
                 }
 
                 if (mKeyguardUnlockAnimationController.isPlayingCannedUnlockAnimation()) {
@@ -530,8 +542,12 @@
         pw.println(mTouchActive);
     }
 
-    private void setExpandAnimationRunning(boolean running) {
+    @VisibleForTesting
+    void setExpandAnimationRunning(boolean running) {
         if (mExpandAnimationRunning != running) {
+            if (running) {
+                mLaunchAnimationTimeout = mClock.uptimeMillis() + 5000;
+            }
             mExpandAnimationRunning = running;
             mNotificationShadeWindowController.setLaunchingActivity(mExpandAnimationRunning);
         }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
index dc506a5..3cce423 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
@@ -123,6 +123,7 @@
     @Mock lateinit var keyEventInteractor: KeyEventInteractor
     private val notificationExpansionRepository = NotificationExpansionRepository()
 
+    private lateinit var fakeClock: FakeSystemClock
     private lateinit var interactionEventHandlerCaptor: ArgumentCaptor<InteractionEventHandler>
     private lateinit var interactionEventHandler: InteractionEventHandler
 
@@ -151,6 +152,7 @@
         featureFlags.set(Flags.LOCKSCREEN_WALLPAPER_DREAM_ENABLED, false)
 
         testScope = TestScope()
+        fakeClock = FakeSystemClock()
         underTest =
             NotificationShadeWindowViewController(
                     lockscreenShadeTransitionController,
@@ -183,7 +185,7 @@
                     primaryBouncerToGoneTransitionViewModel,
                     notificationExpansionRepository,
                     featureFlags,
-                    FakeSystemClock(),
+                    fakeClock,
                     BouncerMessageInteractor(
                         FakeBouncerMessageRepository(),
                         mock(BouncerMessageFactory::class.java),
@@ -332,6 +334,33 @@
         }
 
     @Test
+    fun handleDispatchTouchEvent_launchAnimationRunningTimesOut() =
+        testScope.runTest {
+            // GIVEN touch dispatcher in a state that returns true
+            underTest.setStatusBarViewController(phoneStatusBarViewController)
+            whenever(keyguardUnlockAnimationController.isPlayingCannedUnlockAnimation()).thenReturn(
+                true
+            )
+            assertThat(interactionEventHandler.handleDispatchTouchEvent(DOWN_EVENT)).isTrue()
+
+            // WHEN launch animation is running for 2 seconds
+            fakeClock.setUptimeMillis(10000)
+            underTest.setExpandAnimationRunning(true)
+            fakeClock.advanceTime(2000)
+
+            // THEN touch is ignored
+            assertThat(interactionEventHandler.handleDispatchTouchEvent(DOWN_EVENT)).isFalse()
+
+            // WHEN Launch animation is running for 6 seconds
+            fakeClock.advanceTime(4000)
+
+            // THEN move is ignored, down is handled, and window is notified
+            assertThat(interactionEventHandler.handleDispatchTouchEvent(MOVE_EVENT)).isFalse()
+            assertThat(interactionEventHandler.handleDispatchTouchEvent(DOWN_EVENT)).isTrue()
+            verify(notificationShadeWindowController).setLaunchingActivity(false)
+        }
+
+    @Test
     fun shouldInterceptTouchEvent_statusBarKeyguardViewManagerShouldIntercept() {
         // down event should be intercepted by keyguardViewManager
         whenever(statusBarKeyguardViewManager.shouldInterceptTouchEvent(DOWN_EVENT))
@@ -372,6 +401,7 @@
 
     companion object {
         private val DOWN_EVENT = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0)
+        private val MOVE_EVENT = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_MOVE, 0f, 0f, 0)
         private const val VIEW_BOTTOM = 100
     }
 }