Merge "[multi-shade] Launcher touch integration" into udc-dev
diff --git a/packages/SystemUI/src/com/android/systemui/multishade/domain/interactor/MultiShadeMotionEventInteractor.kt b/packages/SystemUI/src/com/android/systemui/multishade/domain/interactor/MultiShadeMotionEventInteractor.kt
index e352c61..1894bc4 100644
--- a/packages/SystemUI/src/com/android/systemui/multishade/domain/interactor/MultiShadeMotionEventInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/multishade/domain/interactor/MultiShadeMotionEventInteractor.kt
@@ -20,12 +20,16 @@
 import android.view.MotionEvent
 import android.view.ViewConfiguration
 import com.android.systemui.classifier.Classifier
+import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.multishade.shared.math.isZero
 import com.android.systemui.multishade.shared.model.ProxiedInputModel
 import com.android.systemui.plugins.FalsingManager
+import com.android.systemui.shade.ShadeController
 import javax.inject.Inject
 import kotlin.math.abs
 import kotlinx.coroutines.CoroutineScope
@@ -33,6 +37,7 @@
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.launch
 
 /**
  * Encapsulates business logic to handle [MotionEvent]-based user input.
@@ -40,15 +45,31 @@
  * This class is meant purely for the legacy `View`-based system to be able to pass `MotionEvent`s
  * into the newer multi-shade framework for processing.
  */
+@SysUISingleton
 class MultiShadeMotionEventInteractor
 @Inject
 constructor(
     @Application private val applicationContext: Context,
     @Application private val applicationScope: CoroutineScope,
     private val multiShadeInteractor: MultiShadeInteractor,
+    featureFlags: FeatureFlags,
     keyguardTransitionInteractor: KeyguardTransitionInteractor,
     private val falsingManager: FalsingManager,
+    private val shadeController: ShadeController,
 ) {
+    init {
+        if (featureFlags.isEnabled(Flags.DUAL_SHADE)) {
+            applicationScope.launch {
+                multiShadeInteractor.isAnyShadeExpanded.collect {
+                    if (!it && !shadeController.isKeyguard) {
+                        shadeController.makeExpandedInvisible()
+                    } else {
+                        shadeController.makeExpandedVisible(false)
+                    }
+                }
+            }
+        }
+    }
 
     private val isAnyShadeExpanded: StateFlow<Boolean> =
         multiShadeInteractor.isAnyShadeExpanded.stateIn(
@@ -56,6 +77,7 @@
             started = SharingStarted.Eagerly,
             initialValue = false,
         )
+
     private val isBouncerShowing: StateFlow<Boolean> =
         keyguardTransitionInteractor
             .transitionValue(state = KeyguardState.PRIMARY_BOUNCER)
@@ -102,23 +124,7 @@
                 false
             }
             MotionEvent.ACTION_MOVE -> {
-                interactionState?.let {
-                    val pointerIndex = event.findPointerIndex(it.pointerId)
-                    val currentX = event.getX(pointerIndex)
-                    val currentY = event.getY(pointerIndex)
-                    if (!it.isDraggingHorizontally && !it.isDraggingShade) {
-                        val xDistanceTravelled = currentX - it.initialX
-                        val yDistanceTravelled = currentY - it.initialY
-                        val touchSlop = ViewConfiguration.get(applicationContext).scaledTouchSlop
-                        interactionState =
-                            when {
-                                yDistanceTravelled > touchSlop -> it.copy(isDraggingShade = true)
-                                abs(xDistanceTravelled) > touchSlop ->
-                                    it.copy(isDraggingHorizontally = true)
-                                else -> interactionState
-                            }
-                    }
-                }
+                onMove(event)
 
                 // We want to intercept the rest of the gesture if we're dragging the shade.
                 isDraggingShade()
@@ -162,7 +168,8 @@
                         }
                         true
                     } else {
-                        false
+                        onMove(event)
+                        isDraggingShade()
                     }
                 }
                     ?: false
@@ -225,6 +232,32 @@
         }
     }
 
+    /**
+     * Handles [MotionEvent.ACTION_MOVE] and sets whether or not we are dragging shade in our
+     * current interaction
+     *
+     * @param event The [MotionEvent] to handle.
+     */
+    private fun onMove(event: MotionEvent) {
+        interactionState?.let {
+            val pointerIndex = event.findPointerIndex(it.pointerId)
+            val currentX = event.getX(pointerIndex)
+            val currentY = event.getY(pointerIndex)
+            if (!it.isDraggingHorizontally && !it.isDraggingShade) {
+                val xDistanceTravelled = currentX - it.initialX
+                val yDistanceTravelled = currentY - it.initialY
+                val touchSlop = ViewConfiguration.get(applicationContext).scaledTouchSlop
+                interactionState =
+                    when {
+                        yDistanceTravelled > touchSlop -> it.copy(isDraggingShade = true)
+                        abs(xDistanceTravelled) > touchSlop ->
+                            it.copy(isDraggingHorizontally = true)
+                        else -> interactionState
+                    }
+            }
+        }
+    }
+
     private data class InteractionState(
         val initialX: Float,
         val initialY: Float,
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index d2cb762..b7243ae9 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -158,6 +158,7 @@
 import com.android.systemui.media.controls.ui.KeyguardMediaController;
 import com.android.systemui.media.controls.ui.MediaHierarchyManager;
 import com.android.systemui.model.SysUiState;
+import com.android.systemui.multishade.domain.interactor.MultiShadeInteractor;
 import com.android.systemui.navigationbar.NavigationBarController;
 import com.android.systemui.navigationbar.NavigationBarView;
 import com.android.systemui.navigationbar.NavigationModeController;
@@ -392,6 +393,7 @@
     private KeyguardBottomAreaView mKeyguardBottomArea;
     private boolean mExpanding;
     private boolean mSplitShadeEnabled;
+    private boolean mDualShadeEnabled;
     /** The bottom padding reserved for elements of the keyguard measuring notifications. */
     private float mKeyguardNotificationBottomPadding;
     /**
@@ -623,7 +625,9 @@
 
     private final KeyguardTransitionInteractor mKeyguardTransitionInteractor;
     private final KeyguardInteractor mKeyguardInteractor;
+    private final @Nullable MultiShadeInteractor mMultiShadeInteractor;
     private final CoroutineDispatcher mMainDispatcher;
+    private boolean mIsAnyMultiShadeExpanded;
     private boolean mIsOcclusionTransitionRunning = false;
     private int mDreamingToLockscreenTransitionTranslationY;
     private int mOccludedToLockscreenTransitionTranslationY;
@@ -645,6 +649,9 @@
         }
     };
 
+    private final Consumer<Boolean> mMultiShadeExpansionConsumer =
+            (Boolean expanded) -> mIsAnyMultiShadeExpanded = expanded;
+
     private final Consumer<TransitionStep> mDreamingToLockscreenTransition =
             (TransitionStep step) -> {
                 mIsOcclusionTransitionRunning =
@@ -761,6 +768,7 @@
             LockscreenToOccludedTransitionViewModel lockscreenToOccludedTransitionViewModel,
             @Main CoroutineDispatcher mainDispatcher,
             KeyguardTransitionInteractor keyguardTransitionInteractor,
+            Provider<MultiShadeInteractor> multiShadeInteractorProvider,
             DumpManager dumpManager,
             KeyguardLongPressViewModel keyguardLongPressViewModel,
             KeyguardInteractor keyguardInteractor) {
@@ -861,6 +869,8 @@
         mFeatureFlags = featureFlags;
         mAnimateBack = mFeatureFlags.isEnabled(Flags.WM_SHADE_ANIMATE_BACK_GESTURE);
         mTrackpadGestureFeaturesEnabled = mFeatureFlags.isEnabled(Flags.TRACKPAD_GESTURE_FEATURES);
+        mDualShadeEnabled = mFeatureFlags.isEnabled(Flags.DUAL_SHADE);
+        mMultiShadeInteractor = mDualShadeEnabled ? multiShadeInteractorProvider.get() : null;
         mFalsingCollector = falsingCollector;
         mPowerManager = powerManager;
         mWakeUpCoordinator = coordinator;
@@ -1097,6 +1107,11 @@
         mNotificationPanelUnfoldAnimationController.ifPresent(controller ->
                 controller.setup(mNotificationContainerParent));
 
+        if (mDualShadeEnabled) {
+            collectFlow(mView, mMultiShadeInteractor.isAnyShadeExpanded(),
+                    mMultiShadeExpansionConsumer, mMainDispatcher);
+        }
+
         // Dreaming->Lockscreen
         collectFlow(mView, mKeyguardTransitionInteractor.getDreamingToLockscreenTransition(),
                 mDreamingToLockscreenTransition, mMainDispatcher);
@@ -4617,7 +4632,9 @@
             mQsController.setExpandImmediate(false);
             // Close the status bar in the next frame so we can show the end of the
             // animation.
-            mView.post(mMaybeHideExpandedRunnable);
+            if (!mIsAnyMultiShadeExpanded) {
+                mView.post(mMaybeHideExpandedRunnable);
+            }
         }
         mCurrentPanelState = state;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeController.java b/packages/SystemUI/src/com/android/systemui/shade/ShadeController.java
index ad5a68e4d..e08bc33 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeController.java
@@ -64,6 +64,11 @@
     boolean closeShadeIfOpen();
 
     /**
+     * Returns whether the shade state is the keyguard or not.
+     */
+    boolean isKeyguard();
+
+    /**
      * Returns whether the shade is currently open.
      * Even though in the current implementation shade is in expanded state on keyguard, this
      * method makes distinction between shade being truly open and plain keyguard state:
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java
index 826b3ee..c71467b 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java
@@ -154,6 +154,11 @@
     }
 
     @Override
+    public boolean isKeyguard() {
+        return mStatusBarStateController.getState() == StatusBarState.KEYGUARD;
+    }
+
+    @Override
     public boolean isShadeFullyOpen() {
         return mNotificationPanelViewController.isShadeFullyExpanded();
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/multishade/domain/interactor/MultiShadeMotionEventInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/multishade/domain/interactor/MultiShadeMotionEventInteractorTest.kt
index acde887..19f9960 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/multishade/domain/interactor/MultiShadeMotionEventInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/multishade/domain/interactor/MultiShadeMotionEventInteractorTest.kt
@@ -22,6 +22,8 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.classifier.FalsingManagerFake
 import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.Flags
 import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
 import com.android.systemui.keyguard.shared.model.KeyguardState
@@ -31,6 +33,8 @@
 import com.android.systemui.multishade.data.repository.MultiShadeRepository
 import com.android.systemui.multishade.shared.model.ProxiedInputModel
 import com.android.systemui.multishade.shared.model.ShadeId
+import com.android.systemui.shade.ShadeController
+import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.test.TestScope
@@ -42,6 +46,10 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.JUnit4
+import org.mockito.Mock
+import org.mockito.Mockito.anyBoolean
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
 
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
@@ -57,9 +65,11 @@
     private val touchSlop: Int = ViewConfiguration.get(context).scaledTouchSlop
     private lateinit var keyguardTransitionRepository: FakeKeyguardTransitionRepository
     private lateinit var falsingManager: FalsingManagerFake
+    @Mock private lateinit var shadeController: ShadeController
 
     @Before
     fun setUp() {
+        MockitoAnnotations.initMocks(this)
         testScope = TestScope()
         motionEvents = mutableSetOf()
 
@@ -75,18 +85,23 @@
                 repository = repository,
                 inputProxy = inputProxy,
             )
+        val featureFlags = FakeFeatureFlags()
+        featureFlags.set(Flags.DUAL_SHADE, true)
         keyguardTransitionRepository = FakeKeyguardTransitionRepository()
         falsingManager = FalsingManagerFake()
+
         underTest =
             MultiShadeMotionEventInteractor(
                 applicationContext = context,
                 applicationScope = testScope.backgroundScope,
                 multiShadeInteractor = interactor,
+                featureFlags = featureFlags,
                 keyguardTransitionInteractor =
                     KeyguardTransitionInteractor(
                         repository = keyguardTransitionRepository,
                     ),
                 falsingManager = falsingManager,
+                shadeController = shadeController,
             )
     }
 
@@ -96,6 +111,39 @@
     }
 
     @Test
+    fun listenForIsAnyShadeExpanded_expanded_makesWindowViewVisible() =
+        testScope.runTest {
+            whenever(shadeController.isKeyguard).thenReturn(false)
+            repository.setExpansion(ShadeId.LEFT, 0.1f)
+            val expanded by collectLastValue(interactor.isAnyShadeExpanded)
+            assertThat(expanded).isTrue()
+
+            verify(shadeController).makeExpandedVisible(anyBoolean())
+        }
+
+    @Test
+    fun listenForIsAnyShadeExpanded_collapsed_makesWindowViewInvisible() =
+        testScope.runTest {
+            whenever(shadeController.isKeyguard).thenReturn(false)
+            repository.setForceCollapseAll(true)
+            val expanded by collectLastValue(interactor.isAnyShadeExpanded)
+            assertThat(expanded).isFalse()
+
+            verify(shadeController).makeExpandedInvisible()
+        }
+
+    @Test
+    fun listenForIsAnyShadeExpanded_collapsedOnKeyguard_makesWindowViewVisible() =
+        testScope.runTest {
+            whenever(shadeController.isKeyguard).thenReturn(true)
+            repository.setForceCollapseAll(true)
+            val expanded by collectLastValue(interactor.isAnyShadeExpanded)
+            assertThat(expanded).isFalse()
+
+            verify(shadeController).makeExpandedVisible(anyBoolean())
+        }
+
+    @Test
     fun shouldIntercept_initialDown_returnsFalse() =
         testScope.runTest {
             assertThat(underTest.shouldIntercept(motionEvent(MotionEvent.ACTION_DOWN))).isFalse()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
index 1bd13aa..7b37ea0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
@@ -103,6 +103,7 @@
 import com.android.systemui.media.controls.ui.KeyguardMediaController;
 import com.android.systemui.media.controls.ui.MediaHierarchyManager;
 import com.android.systemui.model.SysUiState;
+import com.android.systemui.multishade.domain.interactor.MultiShadeInteractor;
 import com.android.systemui.navigationbar.NavigationBarController;
 import com.android.systemui.navigationbar.NavigationModeController;
 import com.android.systemui.plugins.FalsingManager;
@@ -286,6 +287,7 @@
     @Mock protected GoneToDreamingTransitionViewModel mGoneToDreamingTransitionViewModel;
 
     @Mock protected KeyguardTransitionInteractor mKeyguardTransitionInteractor;
+    @Mock protected MultiShadeInteractor mMultiShadeInteractor;
     @Mock protected KeyguardLongPressViewModel mKeyuardLongPressViewModel;
     @Mock protected AlternateBouncerInteractor mAlternateBouncerInteractor;
     @Mock protected MotionEvent mDownMotionEvent;
@@ -570,6 +572,7 @@
                 mLockscreenToOccludedTransitionViewModel,
                 mMainDispatcher,
                 mKeyguardTransitionInteractor,
+                () -> mMultiShadeInteractor,
                 mDumpManager,
                 mKeyuardLongPressViewModel,
                 mKeyguardInteractor);
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 2a10823..bc8ab1f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
@@ -86,6 +86,7 @@
     @Mock private lateinit var notificationShadeDepthController: NotificationShadeDepthController
     @Mock private lateinit var notificationShadeWindowController: NotificationShadeWindowController
     @Mock private lateinit var keyguardUnlockAnimationController: KeyguardUnlockAnimationController
+    @Mock private lateinit var shadeController: ShadeController
     @Mock private lateinit var ambientState: AmbientState
     @Mock private lateinit var keyguardBouncerViewModel: KeyguardBouncerViewModel
     @Mock private lateinit var stackScrollLayoutController: NotificationStackScrollLayoutController
@@ -173,11 +174,13 @@
                         applicationContext = context,
                         applicationScope = testScope.backgroundScope,
                         multiShadeInteractor = multiShadeInteractor,
+                        featureFlags = featureFlags,
                         keyguardTransitionInteractor =
                             KeyguardTransitionInteractor(
                                 repository = FakeKeyguardTransitionRepository(),
                             ),
                         falsingManager = FalsingManagerFake(),
+                        shadeController = shadeController,
                     )
                 },
             )
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 86660a4..56385b2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
@@ -185,11 +185,13 @@
                         applicationContext = context,
                         applicationScope = testScope.backgroundScope,
                         multiShadeInteractor = multiShadeInteractor,
+                        featureFlags = featureFlags,
                         keyguardTransitionInteractor =
                             KeyguardTransitionInteractor(
                                 repository = FakeKeyguardTransitionRepository(),
                             ),
                         falsingManager = FalsingManagerFake(),
+                        shadeController = shadeController,
                     )
                 },
             )