Merge "Intercept swipe up touches" into main
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index adff590..7f0b899 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -586,6 +586,8 @@
     private boolean mGestureWaitForTouchSlop;
     private boolean mIgnoreXTouchSlop;
     private boolean mExpandLatencyTracking;
+    private boolean mUseExternalTouch = false;
+
     /**
      * Whether we're waking up and will play the delayed doze animation in
      * {@link NotificationWakeUpCoordinator}. If so, we'll want to keep the clock centered until the
@@ -4114,12 +4116,22 @@
 
     /** Sends an external (e.g. Status Bar) intercept touch event to the Shade touch handler. */
     boolean handleExternalInterceptTouch(MotionEvent event) {
-        return mTouchHandler.onInterceptTouchEvent(event);
+        try {
+            mUseExternalTouch = true;
+            return mTouchHandler.onInterceptTouchEvent(event);
+        } finally {
+            mUseExternalTouch = false;
+        }
     }
 
     @Override
     public boolean handleExternalTouch(MotionEvent event) {
-        return mTouchHandler.onTouchEvent(event);
+        try {
+            mUseExternalTouch = true;
+            return mTouchHandler.onTouchEvent(event);
+        } finally {
+            mUseExternalTouch = false;
+        }
     }
 
     @Override
@@ -4706,9 +4718,20 @@
     public final class TouchHandler implements View.OnTouchListener, Gefingerpoken {
         private long mLastTouchDownTime = -1L;
 
-        /** @see ViewGroup#onInterceptTouchEvent(MotionEvent) */
+        /**
+         * With the shade and lockscreen being separated in the view hierarchy, touch handling now
+         * originates with the parent window through {@link #handleExternalTouch}. This allows for
+         * parity with the legacy hierarchy while not undertaking a massive refactoring of touch
+         * handling.
+         *
+         * @see NotificationShadeWindowViewController#didNotificationPanelInterceptEvent
+         */
         @Override
         public boolean onInterceptTouchEvent(MotionEvent event) {
+            if (mFeatureFlags.isEnabled(Flags.MIGRATE_NSSL) && !mUseExternalTouch) {
+                return false;
+            }
+
             mShadeLog.logMotionEvent(event, "NPVC onInterceptTouchEvent");
             if (mQsController.disallowTouches()) {
                 mShadeLog.logMotionEvent(event,
@@ -4861,8 +4884,20 @@
             return onTouchEvent(event);
         }
 
+        /**
+         * With the shade and lockscreen being separated in the view hierarchy, touch handling now
+         * originates with the parent window through {@link #handleExternalTouch}. This allows for
+         * parity with the legacy hierarchy while not undertaking a massive refactoring of touch
+         * handling.
+         *
+         * @see NotificationShadeWindowViewController#didNotificationPanelInterceptEvent
+         */
         @Override
         public boolean onTouchEvent(MotionEvent event) {
+            if (mFeatureFlags.isEnabled(Flags.MIGRATE_NSSL) && !mUseExternalTouch) {
+                return false;
+            }
+
             if (event.getAction() == MotionEvent.ACTION_DOWN) {
                 if (event.getDownTime() == mLastTouchDownTime) {
                     // An issue can occur when swiping down after unlock, where multiple down
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
index 880ba92..96fae14 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
@@ -267,6 +267,9 @@
         }
         mView.setLayoutInsetsController(mNotificationInsetsController);
         mView.setInteractionEventHandler(new NotificationShadeWindowView.InteractionEventHandler() {
+            boolean mUseDragDownHelperForTouch = false;
+            boolean mLastInterceptWasDragDownHelper = false;
+
             @Override
             public Boolean handleDispatchTouchEvent(MotionEvent ev) {
                 if (mStatusBarViewController == null) { // Fix for b/192490822
@@ -360,10 +363,8 @@
                 );
 
                 // In case we start outside of the view bounds (below the status bar), we need to
-                // dispatch
-                // the touch manually as the view system can't accommodate for touches outside of
-                // the
-                // regular view bounds.
+                // dispatch the touch manually as the view system can't accommodate for touches
+                // outside of the regular view bounds.
                 if (isDown && ev.getY() >= mView.getBottom()) {
                     mExpandingBelowNotch = true;
                     expandingBelowNotch = true;
@@ -405,6 +406,15 @@
 
             @Override
             public boolean shouldInterceptTouchEvent(MotionEvent ev) {
+                boolean intercepted = shouldInterceptTouchEventInternal(ev);
+                if (intercepted) {
+                    mUseDragDownHelperForTouch = mLastInterceptWasDragDownHelper;
+                }
+                return intercepted;
+            }
+
+            private boolean shouldInterceptTouchEventInternal(MotionEvent ev) {
+                mLastInterceptWasDragDownHelper = false;
                 if (mStatusBarStateController.isDozing() && !mDozeServiceHost.isPulsing()
                         && !mDockManager.isDocked()) {
                     if (ev.getAction() == MotionEvent.ACTION_DOWN) {
@@ -431,19 +441,36 @@
                 }
 
                 if (mNotificationPanelViewController.isFullyExpanded()
-                        && mDragDownHelper.isDragDownEnabled()
                         && !mService.isBouncerShowing()
                         && !mStatusBarStateController.isDozing()) {
-                    boolean result = mDragDownHelper.onInterceptTouchEvent(ev);
-                    if (result) {
-                        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
-                            mShadeLogger.d("NSWVC: drag down helper intercepted");
+                    if (mDragDownHelper.isDragDownEnabled()) {
+                        // This handles drag down over lockscreen
+                        boolean result = mDragDownHelper.onInterceptTouchEvent(ev);
+                        if (mFeatureFlags.isEnabled(Flags.MIGRATE_NSSL)) {
+                            if (result) {
+                                mLastInterceptWasDragDownHelper = true;
+                                if (ev.getAction() == MotionEvent.ACTION_DOWN) {
+                                    mShadeLogger.d("NSWVC: drag down helper intercepted");
+                                }
+                            } else if (didNotificationPanelInterceptEvent(ev)) {
+                                return true;
+                            }
+                        } else {
+                            if (result) {
+                                if (ev.getAction() == MotionEvent.ACTION_DOWN) {
+                                    mShadeLogger.d("NSWVC: drag down helper intercepted");
+                                }
+                            }
+                        }
+                        return result;
+                    } else {
+                        // This else handles interactions on the full shade while unlocked
+                        if (didNotificationPanelInterceptEvent(ev)) {
+                            return true;
                         }
                     }
-                    return result;
-                } else {
-                    return false;
                 }
+                return false;
             }
 
             @Override
@@ -451,7 +478,9 @@
                 MotionEvent cancellation = MotionEvent.obtain(ev);
                 cancellation.setAction(MotionEvent.ACTION_CANCEL);
                 mStackScrollLayout.onInterceptTouchEvent(cancellation);
-                mNotificationPanelViewController.handleExternalInterceptTouch(cancellation);
+                if (!mFeatureFlags.isEnabled(Flags.MIGRATE_NSSL)) {
+                    mNotificationPanelViewController.handleExternalInterceptTouch(cancellation);
+                }
                 cancellation.recycle();
             }
 
@@ -461,18 +490,27 @@
                 if (mStatusBarStateController.isDozing()) {
                     handled = !mDozeServiceHost.isPulsing();
                 }
-
                 if (mStatusBarKeyguardViewManager.onTouch(ev)) {
                     return true;
                 }
-
-                if (mDragDownHelper.isDragDownEnabled()
-                        || mDragDownHelper.isDraggingDown()) {
-                    // we still want to finish our drag down gesture when locking the screen
-                    return mDragDownHelper.onTouchEvent(ev) || handled;
+                if (mFeatureFlags.isEnabled(Flags.MIGRATE_NSSL)) {
+                    if (mLastInterceptWasDragDownHelper && (mDragDownHelper.isDraggingDown())) {
+                        // we still want to finish our drag down gesture when locking the screen
+                        handled |= mDragDownHelper.onTouchEvent(ev) || handled;
+                    }
+                    if (!handled && mNotificationPanelViewController.handleExternalTouch(ev)) {
+                        return true;
+                    }
                 } else {
-                    return handled;
+                    if (mDragDownHelper.isDragDownEnabled()
+                            || mDragDownHelper.isDraggingDown()) {
+                        // we still want to finish our drag down gesture when locking the screen
+                        return mDragDownHelper.onTouchEvent(ev) || handled;
+                    } else {
+                        return handled;
+                    }
                 }
+                return handled;
             }
 
             @Override
@@ -520,6 +558,20 @@
         mDepthController.onPanelExpansionChanged(currentState);
     }
 
+    private boolean didNotificationPanelInterceptEvent(MotionEvent ev) {
+        if (mFeatureFlags.isEnabled(Flags.MIGRATE_NSSL)) {
+            // Since NotificationStackScrollLayout is now a sibling of notification_panel, we need
+            // to also ask NotificationPanelViewController directly, in order to process swipe up
+            // events originating from notifications
+            if (mNotificationPanelViewController.handleExternalInterceptTouch(ev)) {
+                mShadeLogger.d("NSWVC: NPVC intercepted");
+                return true;
+            }
+        }
+
+        return false;
+    }
+
     public NotificationShadeWindowView getView() {
         return mView;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
index ff0d78f8..d653cb4 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
@@ -70,6 +70,7 @@
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
 import com.android.systemui.fragments.FragmentHostManager;
 import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor;
 import com.android.systemui.media.controls.pipeline.MediaDataManager;
@@ -1776,7 +1777,9 @@
                     // Dragging down on the lockscreen statusbar should prohibit other interactions
                     // immediately, otherwise we'll wait on the touchslop. This is to allow
                     // dragging down to expanded quick settings directly on the lockscreen.
-                    mPanelView.getParent().requestDisallowInterceptTouchEvent(true);
+                    if (!mFeatureFlags.isEnabled(Flags.MIGRATE_NSSL)) {
+                        mPanelView.getParent().requestDisallowInterceptTouchEvent(true);
+                    }
                 }
                 if (mExpansionAnimator != null) {
                     mInitialHeightOnTouch = mExpansionHeight;
@@ -1819,7 +1822,9 @@
                         && Math.abs(h) > Math.abs(x - mInitialTouchX)
                         && shouldQuickSettingsIntercept(
                         mInitialTouchX, mInitialTouchY, h)) {
-                    mPanelView.getParent().requestDisallowInterceptTouchEvent(true);
+                    if (!mFeatureFlags.isEnabled(Flags.MIGRATE_NSSL)) {
+                        mPanelView.getParent().requestDisallowInterceptTouchEvent(true);
+                    }
                     mShadeLog.onQsInterceptMoveQsTrackingEnabled(h);
                     setTracking(true);
                     traceQsJank(true, false);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
index bbca234..d968e71 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -1545,7 +1545,9 @@
         return (v, event) -> {
             mAutoHideController.checkUserAutoHide(event);
             mRemoteInputManager.checkRemoteInputOutside(event);
-            mShadeController.onStatusBarTouch(event);
+            if (!mFeatureFlags.isEnabled(Flags.MIGRATE_NSSL)) {
+                mShadeController.onStatusBarTouch(event);
+            }
             return getNotificationShadeWindowView().onTouchEvent(event);
         };
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
index 6dadd4c3..227f653 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
@@ -59,6 +59,7 @@
 import com.android.keyguard.FaceAuthApiRequestReason;
 import com.android.systemui.DejankUtils;
 import com.android.systemui.R;
+import com.android.systemui.flags.Flags;
 import com.android.systemui.keyguard.shared.model.WakeSleepReason;
 import com.android.systemui.keyguard.shared.model.WakefulnessModel;
 import com.android.systemui.keyguard.shared.model.WakefulnessState;
@@ -1064,7 +1065,18 @@
         mEmptySpaceClickListenerCaptor.getValue().onEmptySpaceClicked(0, 0);
 
         verify(mUpdateMonitor, never()).requestFaceAuth(anyString());
+    }
 
+    @Test
+    public void nsslFlagEnabled_allowOnlyExternalTouches() {
+        when(mFeatureFlags.isEnabled(Flags.MIGRATE_NSSL)).thenReturn(true);
+
+        // This sets the dozing state that is read when onMiddleClicked is eventually invoked.
+        mTouchHandler.onTouch(mock(View.class), mDownMotionEvent);
+        verify(mQsController, never()).disallowTouches();
+
+        mNotificationPanelViewController.handleExternalInterceptTouch(mDownMotionEvent);
+        verify(mQsController).disallowTouches();
     }
 
     @Test
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 39fe498..3da5f6a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
@@ -48,6 +48,7 @@
 import com.android.systemui.log.BouncerLogger
 import com.android.systemui.power.domain.interactor.PowerInteractor
 import com.android.systemui.shade.NotificationShadeWindowView.InteractionEventHandler
+import com.android.systemui.statusbar.DragDownHelper
 import com.android.systemui.statusbar.LockscreenShadeTransitionController
 import com.android.systemui.statusbar.NotificationInsetsController
 import com.android.systemui.statusbar.NotificationShadeDepthController
@@ -124,6 +125,7 @@
     private lateinit var unfoldTransitionProgressProvider:
         Optional<UnfoldTransitionProgressProvider>
     @Mock lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor
+    @Mock lateinit var dragDownHelper: DragDownHelper
     @Mock
     lateinit var primaryBouncerToGoneTransitionViewModel: PrimaryBouncerToGoneTransitionViewModel
     @Mock lateinit var keyEventInteractor: KeyEventInteractor
@@ -137,6 +139,8 @@
 
     private lateinit var testScope: TestScope
 
+    private lateinit var featureFlags: FakeFeatureFlags
+
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
@@ -150,12 +154,13 @@
         whenever(keyguardTransitionInteractor.lockscreenToDreamingTransition)
             .thenReturn(emptyFlow<TransitionStep>())
 
-        val featureFlags = FakeFeatureFlags()
+        featureFlags = FakeFeatureFlags()
         featureFlags.set(Flags.TRACKPAD_GESTURE_COMMON, true)
         featureFlags.set(Flags.TRACKPAD_GESTURE_FEATURES, false)
         featureFlags.set(Flags.SPLIT_SHADE_SUBPIXEL_OPTIMIZATION, true)
         featureFlags.set(Flags.REVAMPED_BOUNCER_MESSAGES, true)
         featureFlags.set(Flags.LOCKSCREEN_WALLPAPER_DREAM_ENABLED, false)
+        featureFlags.set(Flags.MIGRATE_NSSL, false)
 
         testScope = TestScope()
         fakeClock = FakeSystemClock()
@@ -206,6 +211,7 @@
                 keyEventInteractor,
             )
         underTest.setupExpandedStatusBar()
+        underTest.setDragDownHelper(dragDownHelper)
 
         interactionEventHandlerCaptor = ArgumentCaptor.forClass(InteractionEventHandler::class.java)
         verify(view).setInteractionEventHandler(interactionEventHandlerCaptor.capture())
@@ -347,9 +353,8 @@
         testScope.runTest {
             // GIVEN touch dispatcher in a state that returns true
             underTest.setStatusBarViewController(phoneStatusBarViewController)
-            whenever(keyguardUnlockAnimationController.isPlayingCannedUnlockAnimation()).thenReturn(
-                true
-            )
+            whenever(keyguardUnlockAnimationController.isPlayingCannedUnlockAnimation())
+                .thenReturn(true)
             assertThat(interactionEventHandler.handleDispatchTouchEvent(DOWN_EVENT)).isTrue()
 
             // WHEN launch animation is running for 2 seconds
@@ -381,6 +386,32 @@
     }
 
     @Test
+    fun shouldInterceptTouchEvent_notificationPanelViewControllerShouldIntercept() {
+        // GIVEN not dozing
+        whenever(sysuiStatusBarStateController.isDozing()).thenReturn(false)
+        // AND alternate bouncer doesn't want the touch
+        whenever(statusBarKeyguardViewManager.shouldInterceptTouchEvent(DOWN_EVENT))
+            .thenReturn(false)
+        // AND the lock icon doesn't want the touch
+        whenever(lockIconViewController.onInterceptTouchEvent(DOWN_EVENT)).thenReturn(false)
+        // AND the notification panel can accept touches
+        whenever(notificationPanelViewController.isFullyExpanded()).thenReturn(true)
+        whenever(dragDownHelper.isDragDownEnabled).thenReturn(true)
+        whenever(centralSurfaces.isBouncerShowing()).thenReturn(false)
+
+        // AND the drag down helper doesn't want the touch (to pull the shade down)
+        whenever(dragDownHelper.onInterceptTouchEvent(DOWN_EVENT)).thenReturn(false)
+
+        featureFlags.set(Flags.MIGRATE_NSSL, true)
+
+        // WHEN asked if should intercept touch
+        interactionEventHandler.shouldInterceptTouchEvent(DOWN_EVENT)
+
+        // Verify that NPVC gets a chance to use the touch
+        verify(notificationPanelViewController).handleExternalInterceptTouch(DOWN_EVENT)
+    }
+
+    @Test
     fun testGetKeyguardMessageArea() =
         testScope.runTest {
             underTest.keyguardMessageArea
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
index 3031658..04c4b45 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
@@ -164,6 +164,7 @@
         featureFlags.set(Flags.SPLIT_SHADE_SUBPIXEL_OPTIMIZATION, true)
         featureFlags.set(Flags.REVAMPED_BOUNCER_MESSAGES, true)
         featureFlags.set(Flags.LOCKSCREEN_WALLPAPER_DREAM_ENABLED, false)
+        featureFlags.set(Flags.MIGRATE_NSSL, false)
         testScope = TestScope()
         controller =
             NotificationShadeWindowViewController(