Merge "[multi-shade] Launcher touch integration" into udc-dev

This commit is contained in:
TreeHugger Robot
2023-04-03 21:33:24 +00:00
committed by Android (Google) Code Review
8 changed files with 135 additions and 19 deletions

View File

@@ -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,

View File

@@ -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;
}

View File

@@ -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

View File

@@ -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();

View File

@@ -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 {

View File

@@ -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);

View File

@@ -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,
)
},
)

View File

@@ -185,11 +185,13 @@ class NotificationShadeWindowViewTest : SysuiTestCase() {
applicationContext = context,
applicationScope = testScope.backgroundScope,
multiShadeInteractor = multiShadeInteractor,
featureFlags = featureFlags,
keyguardTransitionInteractor =
KeyguardTransitionInteractor(
repository = FakeKeyguardTransitionRepository(),
),
falsingManager = FalsingManagerFake(),
shadeController = shadeController,
)
},
)