[multi-shade] Launcher touch integration
Fixes multi-shade touch interaction on launcher. NPVC will now refrain from hiding NotificationShadeWindowView as long as multi-shade is expanded. Bug: 274828736 Flag: DUAL_SHADE Test: additional MultiShadeMotionEventInteractor test cases forthcoming Test: manually verified dragging down and up on launcher expands & collapses multi shade as expected Change-Id: I14446b06e87258379f67c6643410e3f4125d0ede
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -63,6 +63,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
|
||||
|
||||
@@ -153,6 +153,11 @@ public final class ShadeControllerImpl implements ShadeController {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isKeyguard() {
|
||||
return mStatusBarStateController.getState() == StatusBarState.KEYGUARD;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isShadeFullyOpen() {
|
||||
return mNotificationPanelViewController.isShadeFullyExpanded();
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -95,6 +110,39 @@ class MultiShadeMotionEventInteractorTest : SysuiTestCase() {
|
||||
motionEvents.forEach { motionEvent -> motionEvent.recycle() }
|
||||
}
|
||||
|
||||
@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 {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
},
|
||||
)
|
||||
|
||||
@@ -185,11 +185,13 @@ class NotificationShadeWindowViewTest : SysuiTestCase() {
|
||||
applicationContext = context,
|
||||
applicationScope = testScope.backgroundScope,
|
||||
multiShadeInteractor = multiShadeInteractor,
|
||||
featureFlags = featureFlags,
|
||||
keyguardTransitionInteractor =
|
||||
KeyguardTransitionInteractor(
|
||||
repository = FakeKeyguardTransitionRepository(),
|
||||
),
|
||||
falsingManager = FalsingManagerFake(),
|
||||
shadeController = shadeController,
|
||||
)
|
||||
},
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user