diff options
8 files changed, 135 insertions, 19 deletions
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 e352c613ad86..1894bc4cfeab 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.content.Context 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.SharingStarted 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 @@ import kotlinx.coroutines.flow.stateIn * 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 @@ constructor( started = SharingStarted.Eagerly, initialValue = false, ) + private val isBouncerShowing: StateFlow<Boolean> = keyguardTransitionInteractor .transitionValue(state = KeyguardState.PRIMARY_BOUNCER) @@ -102,23 +124,7 @@ constructor( 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 @@ constructor( } true } else { - false + onMove(event) + isDraggingShade() } } ?: false @@ -225,6 +232,32 @@ constructor( } } + /** + * 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 d2cb762682c5..b7243ae93cc8 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.pipeline.MediaDataManager; 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 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump 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 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump 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 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump } }; + private final Consumer<Boolean> mMultiShadeExpansionConsumer = + (Boolean expanded) -> mIsAnyMultiShadeExpanded = expanded; + private final Consumer<TransitionStep> mDreamingToLockscreenTransition = (TransitionStep step) -> { mIsOcclusionTransitionRunning = @@ -761,6 +768,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump LockscreenToOccludedTransitionViewModel lockscreenToOccludedTransitionViewModel, @Main CoroutineDispatcher mainDispatcher, KeyguardTransitionInteractor keyguardTransitionInteractor, + Provider<MultiShadeInteractor> multiShadeInteractorProvider, DumpManager dumpManager, KeyguardLongPressViewModel keyguardLongPressViewModel, KeyguardInteractor keyguardInteractor) { @@ -861,6 +869,8 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump 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 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump 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 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump 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 ad5a68e4dc3f..e08bc33c1ccd 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 @@ public interface ShadeController { 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 826b3ee7a92d..c71467b99961 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 @@ public final class ShadeControllerImpl implements ShadeController { } @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 acde887818aa..19f9960558b2 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 androidx.test.filters.SmallTest 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.remoteproxy.MultiShadeInputProxy 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.Before 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 @@ class MultiShadeMotionEventInteractorTest : SysuiTestCase() { 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 @@ class MultiShadeMotionEventInteractorTest : SysuiTestCase() { 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 @@ class MultiShadeMotionEventInteractorTest : SysuiTestCase() { } @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 1bd13aadc1f9..7b37ea0c9a1a 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.pipeline.MediaDataManager; 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 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase { @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 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase { 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 2a108239bac5..bc8ab1faf9eb 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 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() { @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 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() { 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 86660a462c59..56385b2ec5da 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 @@ class NotificationShadeWindowViewTest : SysuiTestCase() { applicationContext = context, applicationScope = testScope.backgroundScope, multiShadeInteractor = multiShadeInteractor, + featureFlags = featureFlags, keyguardTransitionInteractor = KeyguardTransitionInteractor( repository = FakeKeyguardTransitionRepository(), ), falsingManager = FalsingManagerFake(), + shadeController = shadeController, ) }, ) |