diff options
10 files changed, 161 insertions, 33 deletions
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenLongPress.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenLongPress.kt index 4555f13a1a2c..cc64a7dfce10 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenLongPress.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenLongPress.kt @@ -19,9 +19,10 @@ package com.android.systemui.keyguard.ui.composable import androidx.compose.foundation.ExperimentalFoundationApi -import androidx.compose.foundation.combinedClickable import androidx.compose.foundation.gestures.awaitEachGesture import androidx.compose.foundation.gestures.awaitFirstDown +import androidx.compose.foundation.gestures.detectTapGestures +import androidx.compose.foundation.indication import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.BoxScope @@ -33,10 +34,12 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.geometry.Rect import androidx.compose.ui.input.pointer.pointerInput import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.android.systemui.communal.ui.compose.extensions.detectLongPressGesture import com.android.systemui.keyguard.ui.viewmodel.KeyguardLongPressViewModel /** Container for lockscreen content that handles long-press to bring up the settings menu. */ @Composable +// TODO(b/344879669): now that it's more generic than long-press, rename it. fun LockscreenLongPress( viewModel: KeyguardLongPressViewModel, modifier: Modifier = Modifier, @@ -50,14 +53,17 @@ fun LockscreenLongPress( Box( modifier = modifier - .combinedClickable( - enabled = isEnabled, - onLongClick = viewModel::onLongPress, - onClick = {}, - interactionSource = interactionSource, - // Passing null for the indication removes the ripple effect. - indication = null, - ) + .pointerInput(isEnabled) { + if (isEnabled) { + detectLongPressGesture { viewModel.onLongPress() } + } + } + .pointerInput(Unit) { + detectTapGestures( + onTap = { viewModel.onClick(it.x, it.y) }, + onDoubleTap = { viewModel.onDoubleClick() }, + ) + } .pointerInput(settingsMenuBounds) { awaitEachGesture { val pointerInputChange = awaitFirstDown() @@ -65,7 +71,9 @@ fun LockscreenLongPress( viewModel.onTouchedOutside() } } - }, + } + // Passing null for the indication removes the ripple effect. + .indication(interactionSource, null) ) { content(setSettingsMenuBounds) } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractorTest.kt index 9d34903d544d..da3de3220267 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractorTest.kt @@ -32,6 +32,7 @@ import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepos import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.kosmos.testScope import com.android.systemui.res.R +import com.android.systemui.shade.pulsingGestureListener import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper import com.android.systemui.testKosmos import com.android.systemui.util.mockito.mock @@ -300,7 +301,8 @@ class KeyguardLongPressInteractorTest : SysuiTestCase() { set(Flags.LOCK_SCREEN_LONG_PRESS_DIRECT_TO_WPP, isOpenWppDirectlyEnabled) }, broadcastDispatcher = fakeBroadcastDispatcher, - accessibilityManager = kosmos.accessibilityManagerWrapper + accessibilityManager = kosmos.accessibilityManagerWrapper, + pulsingGestureListener = kosmos.pulsingGestureListener, ) setUpState() } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractor.kt index bb6215a8b215..5f646a7ce547 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractor.kt @@ -32,6 +32,7 @@ import com.android.systemui.flags.Flags import com.android.systemui.keyguard.data.repository.KeyguardRepository import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.res.R +import com.android.systemui.shade.PulsingGestureListener import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper import javax.inject.Inject import kotlinx.coroutines.CoroutineScope @@ -54,6 +55,7 @@ import kotlinx.coroutines.launch /** Business logic for use-cases related to the keyguard long-press feature. */ @OptIn(ExperimentalCoroutinesApi::class) @SysUISingleton +// TODO(b/344879669): now that it's more generic than long-press, rename it. class KeyguardLongPressInteractor @Inject constructor( @@ -65,6 +67,7 @@ constructor( private val featureFlags: FeatureFlags, broadcastDispatcher: BroadcastDispatcher, private val accessibilityManager: AccessibilityManagerWrapper, + private val pulsingGestureListener: PulsingGestureListener, ) { /** Whether the long-press handling feature should be enabled. */ val isLongPressHandlingEnabled: StateFlow<Boolean> = @@ -166,6 +169,16 @@ constructor( _shouldOpenSettings.value = false } + /** Notifies that the lockscreen has been clicked at position [x], [y]. */ + fun onClick(x: Float, y: Float) { + pulsingGestureListener.onSingleTapUp(x, y) + } + + /** Notifies that the lockscreen has been double clicked. */ + fun onDoubleClick() { + pulsingGestureListener.onDoubleTapEvent() + } + private fun showSettings() { _shouldOpenSettings.value = true } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardLongPressViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardLongPressViewModel.kt index c73931a12455..90fef6fb64e0 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardLongPressViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardLongPressViewModel.kt @@ -24,6 +24,7 @@ import kotlinx.coroutines.flow.Flow /** Models UI state to support the lock screen long-press feature. */ @SysUISingleton +// TODO(b/344879669): now that it's more generic than long-press, rename it. class KeyguardLongPressViewModel @Inject constructor( @@ -45,4 +46,14 @@ constructor( fun onTouchedOutside() { interactor.onTouchedOutside() } + + /** Notifies that the lockscreen has been clicked at position [x], [y]. */ + fun onClick(x: Float, y: Float) { + interactor.onClick(x, y) + } + + /** Notifies that the lockscreen has been double clicked. */ + fun onDoubleClick() { + interactor.onDoubleClick() + } } diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java index e41f99b99e35..9cddf86a40e6 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java @@ -369,7 +369,9 @@ public class NotificationShadeWindowViewController implements Dumpable { } mFalsingCollector.onTouchEvent(ev); - mPulsingWakeupGestureHandler.onTouchEvent(ev); + if (!SceneContainerFlag.isEnabled()) { + mPulsingWakeupGestureHandler.onTouchEvent(ev); + } if (!SceneContainerFlag.isEnabled() && mGlanceableHubContainerController.onTouchEvent(ev)) { diff --git a/packages/SystemUI/src/com/android/systemui/shade/PulsingGestureListener.kt b/packages/SystemUI/src/com/android/systemui/shade/PulsingGestureListener.kt index fe4832f0895b..062327dc2acf 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/PulsingGestureListener.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/PulsingGestureListener.kt @@ -47,17 +47,19 @@ import javax.inject.Inject * display state, wake-ups are handled by [com.android.systemui.doze.DozeSensors]. */ @SysUISingleton -class PulsingGestureListener @Inject constructor( - private val falsingManager: FalsingManager, - private val dockManager: DockManager, - private val powerInteractor: PowerInteractor, - private val ambientDisplayConfiguration: AmbientDisplayConfiguration, - private val statusBarStateController: StatusBarStateController, - private val shadeLogger: ShadeLogger, - private val dozeInteractor: DozeInteractor, - userTracker: UserTracker, - tunerService: TunerService, - dumpManager: DumpManager +class PulsingGestureListener +@Inject +constructor( + private val falsingManager: FalsingManager, + private val dockManager: DockManager, + private val powerInteractor: PowerInteractor, + private val ambientDisplayConfiguration: AmbientDisplayConfiguration, + private val statusBarStateController: StatusBarStateController, + private val shadeLogger: ShadeLogger, + private val dozeInteractor: DozeInteractor, + userTracker: UserTracker, + tunerService: TunerService, + dumpManager: DumpManager ) : GestureDetector.SimpleOnGestureListener(), Dumpable { private var doubleTapEnabled = false private var singleTapEnabled = false @@ -66,21 +68,27 @@ class PulsingGestureListener @Inject constructor( val tunable = Tunable { key: String?, _: String? -> when (key) { Settings.Secure.DOZE_DOUBLE_TAP_GESTURE -> - doubleTapEnabled = ambientDisplayConfiguration.doubleTapGestureEnabled( - userTracker.userId) + doubleTapEnabled = + ambientDisplayConfiguration.doubleTapGestureEnabled(userTracker.userId) Settings.Secure.DOZE_TAP_SCREEN_GESTURE -> - singleTapEnabled = ambientDisplayConfiguration.tapGestureEnabled( - userTracker.userId) + singleTapEnabled = + ambientDisplayConfiguration.tapGestureEnabled(userTracker.userId) } } - tunerService.addTunable(tunable, - Settings.Secure.DOZE_DOUBLE_TAP_GESTURE, - Settings.Secure.DOZE_TAP_SCREEN_GESTURE) + tunerService.addTunable( + tunable, + Settings.Secure.DOZE_DOUBLE_TAP_GESTURE, + Settings.Secure.DOZE_TAP_SCREEN_GESTURE + ) dumpManager.registerDumpable(this) } override fun onSingleTapUp(e: MotionEvent): Boolean { + return onSingleTapUp(e.x, e.y) + } + + fun onSingleTapUp(x: Float, y: Float): Boolean { val isNotDocked = !dockManager.isDocked shadeLogger.logSingleTapUp(statusBarStateController.isDozing, singleTapEnabled, isNotDocked) if (statusBarStateController.isDozing && singleTapEnabled && isNotDocked) { @@ -89,11 +97,13 @@ class PulsingGestureListener @Inject constructor( shadeLogger.logSingleTapUpFalsingState(proximityIsNotNear, isNotAFalseTap) if (proximityIsNotNear && isNotAFalseTap) { shadeLogger.d("Single tap handled, requesting centralSurfaces.wakeUpIfDozing") - dozeInteractor.setLastTapToWakePosition(Point(e.x.toInt(), e.y.toInt())) + dozeInteractor.setLastTapToWakePosition(Point(x.toInt(), y.toInt())) powerInteractor.wakeUpIfDozing("PULSING_SINGLE_TAP", PowerManager.WAKE_REASON_TAP) } + return true } + shadeLogger.d("onSingleTapUp event ignored") return false } @@ -103,10 +113,18 @@ class PulsingGestureListener @Inject constructor( * motion events for a double tap. */ override fun onDoubleTapEvent(e: MotionEvent): Boolean { + if (e.actionMasked != MotionEvent.ACTION_UP) { + return false + } + + return onDoubleTapEvent() + } + + fun onDoubleTapEvent(): Boolean { // React to the [MotionEvent.ACTION_UP] event after double tap is detected. Falsing // checks MUST be on the ACTION_UP event. - if (e.actionMasked == MotionEvent.ACTION_UP && - statusBarStateController.isDozing && + if ( + statusBarStateController.isDozing && (doubleTapEnabled || singleTapEnabled) && !falsingManager.isProximityNear && !falsingManager.isFalseDoubleTap @@ -114,6 +132,7 @@ class PulsingGestureListener @Inject constructor( powerInteractor.wakeUpIfDozing("PULSING_DOUBLE_TAP", PowerManager.WAKE_REASON_TAP) return true } + return false } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt index bdc5fc34158f..68a6e777e97d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt @@ -58,6 +58,7 @@ import com.android.systemui.scene.domain.interactor.sceneInteractor import com.android.systemui.settings.UserFileManager import com.android.systemui.settings.UserTracker import com.android.systemui.shade.domain.interactor.shadeInteractor +import com.android.systemui.shade.pulsingGestureListener import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper import com.android.systemui.statusbar.policy.KeyguardStateController @@ -221,6 +222,7 @@ class KeyguardBottomAreaViewModelTest(flags: FlagsParameterization) : SysuiTestC featureFlags = featureFlags, broadcastDispatcher = broadcastDispatcher, accessibilityManager = accessibilityManager, + pulsingGestureListener = kosmos.pulsingGestureListener, ) underTest = KeyguardBottomAreaViewModel( diff --git a/packages/SystemUI/tests/utils/src/android/hardware/display/AmbientDisplayConfigurationKosmos.kt b/packages/SystemUI/tests/utils/src/android/hardware/display/AmbientDisplayConfigurationKosmos.kt new file mode 100644 index 000000000000..3f3c30f0faec --- /dev/null +++ b/packages/SystemUI/tests/utils/src/android/hardware/display/AmbientDisplayConfigurationKosmos.kt @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.hardware.display + +import android.content.applicationContext +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.Kosmos.Fixture + +val Kosmos.ambientDisplayConfiguration by Fixture { + FakeAmbientDisplayConfiguration(applicationContext) +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractorKosmos.kt index c06f833c9e96..cd0d554e6b1d 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractorKosmos.kt @@ -24,6 +24,7 @@ import com.android.systemui.flags.featureFlagsClassic import com.android.systemui.keyguard.data.repository.keyguardRepository import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.applicationCoroutineScope +import com.android.systemui.shade.pulsingGestureListener val Kosmos.keyguardLongPressInteractor by Kosmos.Fixture { @@ -36,5 +37,6 @@ val Kosmos.keyguardLongPressInteractor by featureFlags = featureFlagsClassic, broadcastDispatcher = broadcastDispatcher, accessibilityManager = accessibilityManagerWrapper, + pulsingGestureListener = pulsingGestureListener, ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/PulsingGestureListenerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/PulsingGestureListenerKosmos.kt new file mode 100644 index 000000000000..4fc22289585f --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/PulsingGestureListenerKosmos.kt @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.shade + +import android.hardware.display.ambientDisplayConfiguration +import com.android.systemui.classifier.falsingManager +import com.android.systemui.dock.dockManager +import com.android.systemui.dump.dumpManager +import com.android.systemui.keyguard.domain.interactor.dozeInteractor +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.Kosmos.Fixture +import com.android.systemui.plugins.statusbar.statusBarStateController +import com.android.systemui.power.domain.interactor.powerInteractor +import com.android.systemui.settings.userTracker +import com.android.systemui.util.mockito.mock + +val Kosmos.pulsingGestureListener by Fixture { + PulsingGestureListener( + falsingManager = falsingManager, + dockManager = dockManager, + powerInteractor = powerInteractor, + ambientDisplayConfiguration = ambientDisplayConfiguration, + statusBarStateController = statusBarStateController, + shadeLogger = mock(), + dozeInteractor = dozeInteractor, + userTracker = userTracker, + tunerService = mock(), + dumpManager = dumpManager, + ) +} |