Loading packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceHapticViewModelTest.kt +81 −51 Original line number Diff line number Diff line Loading @@ -16,21 +16,21 @@ package com.android.systemui.keyguard.data.quickaffordance import android.platform.test.annotations.DisableFlags import android.platform.test.annotations.EnableFlags import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.Flags import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue import com.android.systemui.haptics.FakeVibratorHelper import com.android.systemui.haptics.msdl.fakeMSDLPlayer import com.android.systemui.haptics.vibratorHelper import com.android.systemui.keyguard.domain.interactor.keyguardQuickAffordanceHapticViewModelFactory import com.android.systemui.keyguard.domain.interactor.keyguardQuickAffordanceInteractor import com.android.systemui.keyguard.ui.viewmodel.KeyguardQuickAffordanceHapticViewModel import com.android.systemui.keyguard.ui.viewmodel.KeyguardQuickAffordanceViewModel import com.android.systemui.keyguard.ui.binder.KeyguardBottomAreaVibrations import com.android.systemui.kosmos.testScope import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots import com.android.systemui.testKosmos import com.google.android.msdl.data.model.MSDLToken import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest import org.junit.Test import org.junit.runner.RunWith Loading @@ -41,74 +41,104 @@ class KeyguardQuickAffordanceHapticViewModelTest : SysuiTestCase() { private val kosmos = testKosmos() private val testScope = kosmos.testScope private val slotId = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START private val configKey = "$slotId::home" private val keyguardQuickAffordanceInteractor = kosmos.keyguardQuickAffordanceInteractor private val viewModelFlow = MutableStateFlow(KeyguardQuickAffordanceViewModel(configKey = configKey, slotId = slotId)) private val vibratorHelper = kosmos.vibratorHelper as FakeVibratorHelper private val msdlPlayer = kosmos.fakeMSDLPlayer private val underTest = kosmos.keyguardQuickAffordanceHapticViewModelFactory.create(viewModelFlow) private val underTest = kosmos.keyguardQuickAffordanceHapticViewModelFactory.create() @DisableFlags(Flags.FLAG_MSDL_FEEDBACK) @Test fun whenLaunchingFromTriggeredResult_hapticStateIsLaunch() = fun onQuickAffordanceClick_playsShadeEffect() = testScope.runTest { // GIVEN that the result from triggering the affordance launched an activity or dialog val hapticState by collectLastValue(underTest.quickAffordanceHapticState) keyguardQuickAffordanceInteractor.setLaunchingFromTriggeredResult( KeyguardQuickAffordanceConfig.LaunchingFromTriggeredResult(true, configKey) ) runCurrent() underTest.onQuickAffordanceClick() // THEN the haptic state indicates that a launch haptics must play assertThat(hapticState) .isEqualTo(KeyguardQuickAffordanceHapticViewModel.HapticState.LAUNCH) assertThat(vibratorHelper.hasVibratedWithEffects(KeyguardBottomAreaVibrations.Shake)) .isTrue() assertThat(msdlPlayer.tokensPlayed.isEmpty()).isTrue() } @EnableFlags(Flags.FLAG_MSDL_FEEDBACK) @Test fun whenNotLaunchFromTriggeredResult_hapticStateDoesNotEmit() = fun onQuickAffordanceClick_playsFailureToken() = testScope.runTest { // GIVEN that the result from triggering the affordance did not launch an activity or // dialog val hapticState by collectLastValue(underTest.quickAffordanceHapticState) keyguardQuickAffordanceInteractor.setLaunchingFromTriggeredResult( KeyguardQuickAffordanceConfig.LaunchingFromTriggeredResult(false, configKey) ) runCurrent() underTest.onQuickAffordanceClick() assertThat(msdlPlayer.latestTokenPlayed).isEqualTo(MSDLToken.FAILURE) assertThat(vibratorHelper.totalVibrations).isEqualTo(0) } @EnableFlags(Flags.FLAG_MSDL_FEEDBACK) @Test fun onUpdateActivatedHistory_withoutLongPress_whenToggling_doesNotPlayHaptics() = testScope.runTest { // GIVEN that the isActivated state toggles without a long-press called assertThat(underTest.longPressed).isFalse() underTest.updateActivatedHistory(false) underTest.updateActivatedHistory(true) // THEN there is no haptic state to play any feedback assertThat(hapticState) .isEqualTo(KeyguardQuickAffordanceHapticViewModel.HapticState.NO_HAPTICS) // THEN no haptics play assertThat(msdlPlayer.tokensPlayed.isEmpty()).isTrue() assertThat(vibratorHelper.totalVibrations).isEqualTo(0) } @EnableFlags(Flags.FLAG_MSDL_FEEDBACK) @Test fun onQuickAffordanceTogglesToActivated_hapticStateIsToggleOn() = fun onUpdateActivatedHistory_togglesToActivated_playsMSDLSwitchOnToken() = testScope.runTest { // GIVEN that an affordance toggles from deactivated to activated val hapticState by collectLastValue(underTest.quickAffordanceHapticState) toggleQuickAffordance(on = true) // THEN the haptic state reflects that a toggle on haptics should play assertThat(hapticState) .isEqualTo(KeyguardQuickAffordanceHapticViewModel.HapticState.TOGGLE_ON) // THEN the switch on token plays assertThat(msdlPlayer.latestTokenPlayed).isEqualTo(MSDLToken.SWITCH_ON) assertThat(vibratorHelper.totalVibrations).isEqualTo(0) } @EnableFlags(Flags.FLAG_MSDL_FEEDBACK) @Test fun onQuickAffordanceTogglesToDeactivated_hapticStateIsToggleOff() = fun onUpdateActivatedHistory_togglesToDeactivated_playsMSDLSwitchOffToken() = testScope.runTest { // GIVEN that an affordance toggles from activated to deactivated val hapticState by collectLastValue(underTest.quickAffordanceHapticState) toggleQuickAffordance(on = false) // THEN the haptic state reflects that a toggle off haptics should play assertThat(hapticState) .isEqualTo(KeyguardQuickAffordanceHapticViewModel.HapticState.TOGGLE_OFF) // THEN the switch off token plays assertThat(msdlPlayer.latestTokenPlayed).isEqualTo(MSDLToken.SWITCH_OFF) assertThat(vibratorHelper.totalVibrations).isEqualTo(0) } @DisableFlags(Flags.FLAG_MSDL_FEEDBACK) @Test fun onUpdateActivatedHistory_togglesToActivated__playsActivatedEffect() = testScope.runTest { // GIVEN that an affordance toggles from deactivated to activated toggleQuickAffordance(on = true) // THEN the activated effect plays assertThat( vibratorHelper.hasVibratedWithEffects(KeyguardBottomAreaVibrations.Activated) ) .isTrue() assertThat(msdlPlayer.tokensPlayed.isEmpty()).isTrue() } @DisableFlags(Flags.FLAG_MSDL_FEEDBACK) @Test fun onUpdateActivatedHistory_togglesToDeactivated_playsDeactivatedEffect() = testScope.runTest { // GIVEN that an affordance toggles from activated to deactivated toggleQuickAffordance(on = false) // THEN the deactivated effect plays assertThat( vibratorHelper.hasVibratedWithEffects(KeyguardBottomAreaVibrations.Deactivated) ) .isTrue() assertThat(msdlPlayer.tokensPlayed.isEmpty()).isTrue() } private fun TestScope.toggleQuickAffordance(on: Boolean) { private fun toggleQuickAffordance(on: Boolean) { underTest.onQuickAffordanceLongPress() underTest.updateActivatedHistory(!on) runCurrent() underTest.onQuickAffordanceLongPress() underTest.updateActivatedHistory(on) runCurrent() } } packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt +10 −7 Original line number Diff line number Diff line Loading @@ -19,11 +19,13 @@ package com.android.systemui.keyguard.domain.interactor import android.app.admin.DevicePolicyManager import android.os.UserHandle import android.platform.test.annotations.EnableFlags import android.view.accessibility.AccessibilityManager import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.internal.widget.LockPatternUtils import com.android.keyguard.logging.KeyguardQuickAffordancesLogger import com.android.systemui.Flags import com.android.systemui.SysuiTestCase import com.android.systemui.animation.DialogTransitionAnimator import com.android.systemui.common.shared.model.ContentDescription Loading @@ -34,6 +36,7 @@ import com.android.systemui.dock.DockManager import com.android.systemui.dock.DockManagerFake import com.android.systemui.flags.EnableSceneContainer import com.android.systemui.flags.FakeFeatureFlags import com.android.systemui.haptics.msdl.fakeMSDLPlayer import com.android.systemui.keyguard.data.quickaffordance.BuiltInKeyguardQuickAffordanceKeys import com.android.systemui.keyguard.data.quickaffordance.FakeKeyguardQuickAffordanceConfig import com.android.systemui.keyguard.data.quickaffordance.FakeKeyguardQuickAffordanceProviderClientFactory Loading Loading @@ -65,6 +68,7 @@ import com.android.systemui.util.FakeSharedPreferences import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.whenever import com.android.systemui.util.settings.fakeSettings import com.google.android.msdl.data.model.MSDLToken import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.test.runCurrent Loading @@ -84,6 +88,7 @@ class KeyguardQuickAffordanceInteractorTest : SysuiTestCase() { private val kosmos = testKosmos() private val testScope = kosmos.testScope private val settings = kosmos.fakeSettings private val msdlPlayer = kosmos.fakeMSDLPlayer @Mock private lateinit var lockPatternUtils: LockPatternUtils @Mock private lateinit var keyguardStateController: KeyguardStateController Loading Loading @@ -198,6 +203,7 @@ class KeyguardQuickAffordanceInteractorTest : SysuiTestCase() { appContext = context, accessibilityManager = accessibilityManager, sceneInteractor = { kosmos.sceneInteractor }, msdlPlayer = msdlPlayer, ) kosmos.keyguardQuickAffordanceInteractor = underTest Loading Loading @@ -782,8 +788,9 @@ class KeyguardQuickAffordanceInteractorTest : SysuiTestCase() { assertThat(launchingAffordance).isFalse() } @EnableFlags(Flags.FLAG_MSDL_FEEDBACK) @Test fun onQuickAffordanceTriggered_updatesLaunchingFromTriggeredResult() = fun onQuickAffordanceTriggered_onLaunched_playsMSDLLongPress() = testScope.runTest { // WHEN selecting and triggering a quick affordance at a slot val key = homeControls.key Loading @@ -796,12 +803,8 @@ class KeyguardQuickAffordanceInteractorTest : SysuiTestCase() { runCurrent() underTest.onQuickAffordanceTriggered(encodedKey, expandable = null, slot) // THEN the latest triggered result shows that an action launched for the same key and // slot val launchingFromTriggeredResult by collectLastValue(underTest.launchingFromTriggeredResult) assertThat(launchingFromTriggeredResult?.launched).isEqualTo(actionLaunched) assertThat(launchingFromTriggeredResult?.configKey).isEqualTo(encodedKey) // THEN long-press token plays since the action launched. assertThat(msdlPlayer.latestTokenPlayed).isEqualTo(MSDLToken.LONG_PRESS) } companion object { Loading packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt +13 −33 Original line number Diff line number Diff line Loading @@ -27,6 +27,7 @@ import com.android.app.tracing.coroutines.withContextTraced as withContext import com.android.compose.animation.scene.ObservableTransitionState import com.android.internal.widget.LockPatternUtils import com.android.keyguard.logging.KeyguardQuickAffordancesLogger import com.android.systemui.Flags.msdlFeedback import com.android.systemui.animation.DialogTransitionAnimator import com.android.systemui.animation.Expandable import com.android.systemui.dagger.SysUISingleton Loading Loading @@ -57,11 +58,12 @@ import com.android.systemui.shared.customization.data.content.CustomizationProvi import com.android.systemui.shared.quickaffordance.shared.model.KeyguardPreviewConstants.KEYGUARD_QUICK_AFFORDANCE_ID_NONE import com.android.systemui.statusbar.phone.SystemUIDialog import com.android.systemui.statusbar.policy.KeyguardStateController import com.google.android.msdl.data.model.MSDLToken import com.google.android.msdl.domain.MSDLPlayer import dagger.Lazy import javax.inject.Inject import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.combine Loading Loading @@ -93,6 +95,7 @@ constructor( @Background private val backgroundDispatcher: CoroutineDispatcher, @ShadeDisplayAware private val appContext: Context, private val sceneInteractor: Lazy<SceneInteractor>, private val msdlPlayer: MSDLPlayer, ) { /** * Whether a quick affordance is being launched. Quick Affordances are interactive lockscreen UI Loading @@ -100,14 +103,6 @@ constructor( */ val launchingAffordance: StateFlow<Boolean> = repository.get().launchingAffordance.asStateFlow() /** * Whether a [KeyguardQuickAffordanceConfig.OnTriggeredResult] indicated that the system * launched an activity or showed a dialog. */ private val _launchingFromTriggeredResult = MutableStateFlow<KeyguardQuickAffordanceConfig.LaunchingFromTriggeredResult?>(null) val launchingFromTriggeredResult = _launchingFromTriggeredResult.asStateFlow() /** * Whether the UI should use the long press gesture to activate quick affordances. * Loading Loading @@ -199,42 +194,27 @@ constructor( when (val result = config.onTriggered(expandable)) { is KeyguardQuickAffordanceConfig.OnTriggeredResult.StartActivity -> { setLaunchingFromTriggeredResult( KeyguardQuickAffordanceConfig.LaunchingFromTriggeredResult( launched = true, configKey, ) ) launchQuickAffordance( intent = result.intent, canShowWhileLocked = result.canShowWhileLocked, expandable = expandable, ) if (msdlFeedback()) { msdlPlayer.playToken(MSDLToken.LONG_PRESS) } } is KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled -> { setLaunchingFromTriggeredResult( KeyguardQuickAffordanceConfig.LaunchingFromTriggeredResult( result.actionLaunched, configKey, ) ) if (result.actionLaunched && msdlFeedback()) { msdlPlayer.playToken(MSDLToken.LONG_PRESS) } } is KeyguardQuickAffordanceConfig.OnTriggeredResult.ShowDialog -> { setLaunchingFromTriggeredResult( KeyguardQuickAffordanceConfig.LaunchingFromTriggeredResult( launched = true, configKey, ) ) showDialog(result.dialog, result.expandable) if (msdlFeedback()) { msdlPlayer.playToken(MSDLToken.LONG_PRESS) } showDialog(result.dialog, result.expandable) } } fun setLaunchingFromTriggeredResult( launchingResult: KeyguardQuickAffordanceConfig.LaunchingFromTriggeredResult? ) { _launchingFromTriggeredResult.value = launchingResult } /** Loading packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceViewBinder.kt +9 −60 Original line number Diff line number Diff line Loading @@ -20,7 +20,6 @@ package com.android.systemui.keyguard.ui.binder import android.annotation.SuppressLint import android.content.res.ColorStateList import android.graphics.drawable.Animatable2 import android.os.VibrationEffect import android.util.Size import android.view.View import android.view.ViewGroup Loading @@ -34,7 +33,6 @@ import androidx.lifecycle.Lifecycle import androidx.lifecycle.repeatOnLifecycle import com.android.app.tracing.coroutines.launchTraced as launch import com.android.keyguard.logging.KeyguardQuickAffordancesLogger import com.android.systemui.Flags import com.android.systemui.animation.Expandable import com.android.systemui.animation.view.LaunchableImageView import com.android.systemui.common.shared.model.Icon Loading @@ -48,13 +46,11 @@ import com.android.systemui.plugins.FalsingManager import com.android.systemui.res.R import com.android.systemui.statusbar.VibratorHelper import com.android.systemui.util.doOnEnd import com.google.android.msdl.data.model.MSDLToken import com.google.android.msdl.domain.MSDLPlayer import javax.inject.Inject import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.map /** This is only for a SINGLE Quick affordance */ Loading Loading @@ -93,12 +89,8 @@ constructor( ): Binding { val button = view as ImageView val configurationBasedDimensions = MutableStateFlow(loadFromResources(view)) val hapticsViewModel = if (Flags.msdlFeedback()) { hapticsViewModelFactory.create(viewModel) } else { null } val hapticsViewModel = hapticsViewModelFactory.create() val disposableHandle = view.repeatWhenAttached { repeatOnLifecycle(Lifecycle.State.STARTED) { Loading @@ -107,9 +99,9 @@ constructor( updateButton( view = button, viewModel = buttonModel, hapticsViewModel, messageDisplayer = messageDisplayer, ) hapticsViewModel?.updateActivatedHistory(buttonModel.isActivated) } } Loading @@ -125,32 +117,6 @@ constructor( } } } if (Flags.msdlFeedback()) { launch { hapticsViewModel ?.quickAffordanceHapticState ?.filter { it != KeyguardQuickAffordanceHapticViewModel.HapticState .NO_HAPTICS } ?.collect { state -> when (state) { KeyguardQuickAffordanceHapticViewModel.HapticState .TOGGLE_ON -> msdlPlayer.playToken(MSDLToken.SWITCH_ON) KeyguardQuickAffordanceHapticViewModel.HapticState .TOGGLE_OFF -> msdlPlayer.playToken(MSDLToken.SWITCH_OFF) KeyguardQuickAffordanceHapticViewModel.HapticState.LAUNCH -> msdlPlayer.playToken(MSDLToken.LONG_PRESS) KeyguardQuickAffordanceHapticViewModel.HapticState .NO_HAPTICS -> Unit } hapticsViewModel.resetLaunchingFromTriggeredResult() } } } } } Loading @@ -170,8 +136,10 @@ constructor( private fun updateButton( view: ImageView, viewModel: KeyguardQuickAffordanceViewModel, hapticsViewModel: KeyguardQuickAffordanceHapticViewModel, messageDisplayer: (Int) -> Unit, ) { hapticsViewModel.updateActivatedHistory(viewModel.isActivated) logger.logUpdate(viewModel) if (!viewModel.isVisible) { view.isInvisible = true Loading Loading @@ -263,16 +231,15 @@ constructor( shakeAnimator.doOnEnd { view.translationX = 0f } shakeAnimator.start() vibratorHelper?.playFeedback(KeyguardBottomAreaVibrations.Shake, msdlPlayer) hapticsViewModel.onQuickAffordanceClick() logger.logQuickAffordanceTapped(viewModel.configKey) } view.onLongClickListener = OnLongClickListener( falsingManager, viewModel, vibratorHelper, hapticsViewModel, onTouchListener, msdlPlayer, ) } else { view.setOnClickListener(OnClickListener(viewModel, checkNotNull(falsingManager))) Loading Loading @@ -332,9 +299,8 @@ constructor( private class OnLongClickListener( private val falsingManager: FalsingManager?, private val viewModel: KeyguardQuickAffordanceViewModel, private val vibratorHelper: VibratorHelper?, private val hapticsViewModel: KeyguardQuickAffordanceHapticViewModel, private val onTouchListener: KeyguardQuickAffordanceOnTouchListener, private val msdlPlayer: MSDLPlayer, ) : View.OnLongClickListener { override fun onLongClick(view: View): Boolean { if (falsingManager?.isFalseLongTap(FalsingManager.MODERATE_PENALTY) == true) { Loading @@ -342,6 +308,7 @@ constructor( } if (viewModel.configKey != null) { hapticsViewModel.onQuickAffordanceLongPress() viewModel.onClicked( KeyguardQuickAffordanceViewModel.OnClickedParameters( configKey = viewModel.configKey, Loading @@ -349,14 +316,6 @@ constructor( slotId = viewModel.slotId, ) ) vibratorHelper?.playFeedback( if (viewModel.isActivated) { KeyguardBottomAreaVibrations.Activated } else { KeyguardBottomAreaVibrations.Deactivated }, msdlPlayer, ) } onTouchListener.cancel() Loading @@ -368,13 +327,3 @@ constructor( private data class ConfigurationBasedDimensions(val buttonSizePx: Size) } private fun VibratorHelper.playFeedback(effect: VibrationEffect, msdlPlayer: MSDLPlayer) { if (!Flags.msdlFeedback()) { vibrate(effect) } else { if (effect == KeyguardBottomAreaVibrations.Shake) { msdlPlayer.playToken(MSDLToken.FAILURE) } } } packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordanceHapticViewModel.kt +43 −59 File changed.Preview size limit exceeded, changes collapsed. Show changes Loading
packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceHapticViewModelTest.kt +81 −51 Original line number Diff line number Diff line Loading @@ -16,21 +16,21 @@ package com.android.systemui.keyguard.data.quickaffordance import android.platform.test.annotations.DisableFlags import android.platform.test.annotations.EnableFlags import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.Flags import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue import com.android.systemui.haptics.FakeVibratorHelper import com.android.systemui.haptics.msdl.fakeMSDLPlayer import com.android.systemui.haptics.vibratorHelper import com.android.systemui.keyguard.domain.interactor.keyguardQuickAffordanceHapticViewModelFactory import com.android.systemui.keyguard.domain.interactor.keyguardQuickAffordanceInteractor import com.android.systemui.keyguard.ui.viewmodel.KeyguardQuickAffordanceHapticViewModel import com.android.systemui.keyguard.ui.viewmodel.KeyguardQuickAffordanceViewModel import com.android.systemui.keyguard.ui.binder.KeyguardBottomAreaVibrations import com.android.systemui.kosmos.testScope import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots import com.android.systemui.testKosmos import com.google.android.msdl.data.model.MSDLToken import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest import org.junit.Test import org.junit.runner.RunWith Loading @@ -41,74 +41,104 @@ class KeyguardQuickAffordanceHapticViewModelTest : SysuiTestCase() { private val kosmos = testKosmos() private val testScope = kosmos.testScope private val slotId = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START private val configKey = "$slotId::home" private val keyguardQuickAffordanceInteractor = kosmos.keyguardQuickAffordanceInteractor private val viewModelFlow = MutableStateFlow(KeyguardQuickAffordanceViewModel(configKey = configKey, slotId = slotId)) private val vibratorHelper = kosmos.vibratorHelper as FakeVibratorHelper private val msdlPlayer = kosmos.fakeMSDLPlayer private val underTest = kosmos.keyguardQuickAffordanceHapticViewModelFactory.create(viewModelFlow) private val underTest = kosmos.keyguardQuickAffordanceHapticViewModelFactory.create() @DisableFlags(Flags.FLAG_MSDL_FEEDBACK) @Test fun whenLaunchingFromTriggeredResult_hapticStateIsLaunch() = fun onQuickAffordanceClick_playsShadeEffect() = testScope.runTest { // GIVEN that the result from triggering the affordance launched an activity or dialog val hapticState by collectLastValue(underTest.quickAffordanceHapticState) keyguardQuickAffordanceInteractor.setLaunchingFromTriggeredResult( KeyguardQuickAffordanceConfig.LaunchingFromTriggeredResult(true, configKey) ) runCurrent() underTest.onQuickAffordanceClick() // THEN the haptic state indicates that a launch haptics must play assertThat(hapticState) .isEqualTo(KeyguardQuickAffordanceHapticViewModel.HapticState.LAUNCH) assertThat(vibratorHelper.hasVibratedWithEffects(KeyguardBottomAreaVibrations.Shake)) .isTrue() assertThat(msdlPlayer.tokensPlayed.isEmpty()).isTrue() } @EnableFlags(Flags.FLAG_MSDL_FEEDBACK) @Test fun whenNotLaunchFromTriggeredResult_hapticStateDoesNotEmit() = fun onQuickAffordanceClick_playsFailureToken() = testScope.runTest { // GIVEN that the result from triggering the affordance did not launch an activity or // dialog val hapticState by collectLastValue(underTest.quickAffordanceHapticState) keyguardQuickAffordanceInteractor.setLaunchingFromTriggeredResult( KeyguardQuickAffordanceConfig.LaunchingFromTriggeredResult(false, configKey) ) runCurrent() underTest.onQuickAffordanceClick() assertThat(msdlPlayer.latestTokenPlayed).isEqualTo(MSDLToken.FAILURE) assertThat(vibratorHelper.totalVibrations).isEqualTo(0) } @EnableFlags(Flags.FLAG_MSDL_FEEDBACK) @Test fun onUpdateActivatedHistory_withoutLongPress_whenToggling_doesNotPlayHaptics() = testScope.runTest { // GIVEN that the isActivated state toggles without a long-press called assertThat(underTest.longPressed).isFalse() underTest.updateActivatedHistory(false) underTest.updateActivatedHistory(true) // THEN there is no haptic state to play any feedback assertThat(hapticState) .isEqualTo(KeyguardQuickAffordanceHapticViewModel.HapticState.NO_HAPTICS) // THEN no haptics play assertThat(msdlPlayer.tokensPlayed.isEmpty()).isTrue() assertThat(vibratorHelper.totalVibrations).isEqualTo(0) } @EnableFlags(Flags.FLAG_MSDL_FEEDBACK) @Test fun onQuickAffordanceTogglesToActivated_hapticStateIsToggleOn() = fun onUpdateActivatedHistory_togglesToActivated_playsMSDLSwitchOnToken() = testScope.runTest { // GIVEN that an affordance toggles from deactivated to activated val hapticState by collectLastValue(underTest.quickAffordanceHapticState) toggleQuickAffordance(on = true) // THEN the haptic state reflects that a toggle on haptics should play assertThat(hapticState) .isEqualTo(KeyguardQuickAffordanceHapticViewModel.HapticState.TOGGLE_ON) // THEN the switch on token plays assertThat(msdlPlayer.latestTokenPlayed).isEqualTo(MSDLToken.SWITCH_ON) assertThat(vibratorHelper.totalVibrations).isEqualTo(0) } @EnableFlags(Flags.FLAG_MSDL_FEEDBACK) @Test fun onQuickAffordanceTogglesToDeactivated_hapticStateIsToggleOff() = fun onUpdateActivatedHistory_togglesToDeactivated_playsMSDLSwitchOffToken() = testScope.runTest { // GIVEN that an affordance toggles from activated to deactivated val hapticState by collectLastValue(underTest.quickAffordanceHapticState) toggleQuickAffordance(on = false) // THEN the haptic state reflects that a toggle off haptics should play assertThat(hapticState) .isEqualTo(KeyguardQuickAffordanceHapticViewModel.HapticState.TOGGLE_OFF) // THEN the switch off token plays assertThat(msdlPlayer.latestTokenPlayed).isEqualTo(MSDLToken.SWITCH_OFF) assertThat(vibratorHelper.totalVibrations).isEqualTo(0) } @DisableFlags(Flags.FLAG_MSDL_FEEDBACK) @Test fun onUpdateActivatedHistory_togglesToActivated__playsActivatedEffect() = testScope.runTest { // GIVEN that an affordance toggles from deactivated to activated toggleQuickAffordance(on = true) // THEN the activated effect plays assertThat( vibratorHelper.hasVibratedWithEffects(KeyguardBottomAreaVibrations.Activated) ) .isTrue() assertThat(msdlPlayer.tokensPlayed.isEmpty()).isTrue() } @DisableFlags(Flags.FLAG_MSDL_FEEDBACK) @Test fun onUpdateActivatedHistory_togglesToDeactivated_playsDeactivatedEffect() = testScope.runTest { // GIVEN that an affordance toggles from activated to deactivated toggleQuickAffordance(on = false) // THEN the deactivated effect plays assertThat( vibratorHelper.hasVibratedWithEffects(KeyguardBottomAreaVibrations.Deactivated) ) .isTrue() assertThat(msdlPlayer.tokensPlayed.isEmpty()).isTrue() } private fun TestScope.toggleQuickAffordance(on: Boolean) { private fun toggleQuickAffordance(on: Boolean) { underTest.onQuickAffordanceLongPress() underTest.updateActivatedHistory(!on) runCurrent() underTest.onQuickAffordanceLongPress() underTest.updateActivatedHistory(on) runCurrent() } }
packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt +10 −7 Original line number Diff line number Diff line Loading @@ -19,11 +19,13 @@ package com.android.systemui.keyguard.domain.interactor import android.app.admin.DevicePolicyManager import android.os.UserHandle import android.platform.test.annotations.EnableFlags import android.view.accessibility.AccessibilityManager import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.internal.widget.LockPatternUtils import com.android.keyguard.logging.KeyguardQuickAffordancesLogger import com.android.systemui.Flags import com.android.systemui.SysuiTestCase import com.android.systemui.animation.DialogTransitionAnimator import com.android.systemui.common.shared.model.ContentDescription Loading @@ -34,6 +36,7 @@ import com.android.systemui.dock.DockManager import com.android.systemui.dock.DockManagerFake import com.android.systemui.flags.EnableSceneContainer import com.android.systemui.flags.FakeFeatureFlags import com.android.systemui.haptics.msdl.fakeMSDLPlayer import com.android.systemui.keyguard.data.quickaffordance.BuiltInKeyguardQuickAffordanceKeys import com.android.systemui.keyguard.data.quickaffordance.FakeKeyguardQuickAffordanceConfig import com.android.systemui.keyguard.data.quickaffordance.FakeKeyguardQuickAffordanceProviderClientFactory Loading Loading @@ -65,6 +68,7 @@ import com.android.systemui.util.FakeSharedPreferences import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.whenever import com.android.systemui.util.settings.fakeSettings import com.google.android.msdl.data.model.MSDLToken import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.test.runCurrent Loading @@ -84,6 +88,7 @@ class KeyguardQuickAffordanceInteractorTest : SysuiTestCase() { private val kosmos = testKosmos() private val testScope = kosmos.testScope private val settings = kosmos.fakeSettings private val msdlPlayer = kosmos.fakeMSDLPlayer @Mock private lateinit var lockPatternUtils: LockPatternUtils @Mock private lateinit var keyguardStateController: KeyguardStateController Loading Loading @@ -198,6 +203,7 @@ class KeyguardQuickAffordanceInteractorTest : SysuiTestCase() { appContext = context, accessibilityManager = accessibilityManager, sceneInteractor = { kosmos.sceneInteractor }, msdlPlayer = msdlPlayer, ) kosmos.keyguardQuickAffordanceInteractor = underTest Loading Loading @@ -782,8 +788,9 @@ class KeyguardQuickAffordanceInteractorTest : SysuiTestCase() { assertThat(launchingAffordance).isFalse() } @EnableFlags(Flags.FLAG_MSDL_FEEDBACK) @Test fun onQuickAffordanceTriggered_updatesLaunchingFromTriggeredResult() = fun onQuickAffordanceTriggered_onLaunched_playsMSDLLongPress() = testScope.runTest { // WHEN selecting and triggering a quick affordance at a slot val key = homeControls.key Loading @@ -796,12 +803,8 @@ class KeyguardQuickAffordanceInteractorTest : SysuiTestCase() { runCurrent() underTest.onQuickAffordanceTriggered(encodedKey, expandable = null, slot) // THEN the latest triggered result shows that an action launched for the same key and // slot val launchingFromTriggeredResult by collectLastValue(underTest.launchingFromTriggeredResult) assertThat(launchingFromTriggeredResult?.launched).isEqualTo(actionLaunched) assertThat(launchingFromTriggeredResult?.configKey).isEqualTo(encodedKey) // THEN long-press token plays since the action launched. assertThat(msdlPlayer.latestTokenPlayed).isEqualTo(MSDLToken.LONG_PRESS) } companion object { Loading
packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt +13 −33 Original line number Diff line number Diff line Loading @@ -27,6 +27,7 @@ import com.android.app.tracing.coroutines.withContextTraced as withContext import com.android.compose.animation.scene.ObservableTransitionState import com.android.internal.widget.LockPatternUtils import com.android.keyguard.logging.KeyguardQuickAffordancesLogger import com.android.systemui.Flags.msdlFeedback import com.android.systemui.animation.DialogTransitionAnimator import com.android.systemui.animation.Expandable import com.android.systemui.dagger.SysUISingleton Loading Loading @@ -57,11 +58,12 @@ import com.android.systemui.shared.customization.data.content.CustomizationProvi import com.android.systemui.shared.quickaffordance.shared.model.KeyguardPreviewConstants.KEYGUARD_QUICK_AFFORDANCE_ID_NONE import com.android.systemui.statusbar.phone.SystemUIDialog import com.android.systemui.statusbar.policy.KeyguardStateController import com.google.android.msdl.data.model.MSDLToken import com.google.android.msdl.domain.MSDLPlayer import dagger.Lazy import javax.inject.Inject import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.combine Loading Loading @@ -93,6 +95,7 @@ constructor( @Background private val backgroundDispatcher: CoroutineDispatcher, @ShadeDisplayAware private val appContext: Context, private val sceneInteractor: Lazy<SceneInteractor>, private val msdlPlayer: MSDLPlayer, ) { /** * Whether a quick affordance is being launched. Quick Affordances are interactive lockscreen UI Loading @@ -100,14 +103,6 @@ constructor( */ val launchingAffordance: StateFlow<Boolean> = repository.get().launchingAffordance.asStateFlow() /** * Whether a [KeyguardQuickAffordanceConfig.OnTriggeredResult] indicated that the system * launched an activity or showed a dialog. */ private val _launchingFromTriggeredResult = MutableStateFlow<KeyguardQuickAffordanceConfig.LaunchingFromTriggeredResult?>(null) val launchingFromTriggeredResult = _launchingFromTriggeredResult.asStateFlow() /** * Whether the UI should use the long press gesture to activate quick affordances. * Loading Loading @@ -199,42 +194,27 @@ constructor( when (val result = config.onTriggered(expandable)) { is KeyguardQuickAffordanceConfig.OnTriggeredResult.StartActivity -> { setLaunchingFromTriggeredResult( KeyguardQuickAffordanceConfig.LaunchingFromTriggeredResult( launched = true, configKey, ) ) launchQuickAffordance( intent = result.intent, canShowWhileLocked = result.canShowWhileLocked, expandable = expandable, ) if (msdlFeedback()) { msdlPlayer.playToken(MSDLToken.LONG_PRESS) } } is KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled -> { setLaunchingFromTriggeredResult( KeyguardQuickAffordanceConfig.LaunchingFromTriggeredResult( result.actionLaunched, configKey, ) ) if (result.actionLaunched && msdlFeedback()) { msdlPlayer.playToken(MSDLToken.LONG_PRESS) } } is KeyguardQuickAffordanceConfig.OnTriggeredResult.ShowDialog -> { setLaunchingFromTriggeredResult( KeyguardQuickAffordanceConfig.LaunchingFromTriggeredResult( launched = true, configKey, ) ) showDialog(result.dialog, result.expandable) if (msdlFeedback()) { msdlPlayer.playToken(MSDLToken.LONG_PRESS) } showDialog(result.dialog, result.expandable) } } fun setLaunchingFromTriggeredResult( launchingResult: KeyguardQuickAffordanceConfig.LaunchingFromTriggeredResult? ) { _launchingFromTriggeredResult.value = launchingResult } /** Loading
packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceViewBinder.kt +9 −60 Original line number Diff line number Diff line Loading @@ -20,7 +20,6 @@ package com.android.systemui.keyguard.ui.binder import android.annotation.SuppressLint import android.content.res.ColorStateList import android.graphics.drawable.Animatable2 import android.os.VibrationEffect import android.util.Size import android.view.View import android.view.ViewGroup Loading @@ -34,7 +33,6 @@ import androidx.lifecycle.Lifecycle import androidx.lifecycle.repeatOnLifecycle import com.android.app.tracing.coroutines.launchTraced as launch import com.android.keyguard.logging.KeyguardQuickAffordancesLogger import com.android.systemui.Flags import com.android.systemui.animation.Expandable import com.android.systemui.animation.view.LaunchableImageView import com.android.systemui.common.shared.model.Icon Loading @@ -48,13 +46,11 @@ import com.android.systemui.plugins.FalsingManager import com.android.systemui.res.R import com.android.systemui.statusbar.VibratorHelper import com.android.systemui.util.doOnEnd import com.google.android.msdl.data.model.MSDLToken import com.google.android.msdl.domain.MSDLPlayer import javax.inject.Inject import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.map /** This is only for a SINGLE Quick affordance */ Loading Loading @@ -93,12 +89,8 @@ constructor( ): Binding { val button = view as ImageView val configurationBasedDimensions = MutableStateFlow(loadFromResources(view)) val hapticsViewModel = if (Flags.msdlFeedback()) { hapticsViewModelFactory.create(viewModel) } else { null } val hapticsViewModel = hapticsViewModelFactory.create() val disposableHandle = view.repeatWhenAttached { repeatOnLifecycle(Lifecycle.State.STARTED) { Loading @@ -107,9 +99,9 @@ constructor( updateButton( view = button, viewModel = buttonModel, hapticsViewModel, messageDisplayer = messageDisplayer, ) hapticsViewModel?.updateActivatedHistory(buttonModel.isActivated) } } Loading @@ -125,32 +117,6 @@ constructor( } } } if (Flags.msdlFeedback()) { launch { hapticsViewModel ?.quickAffordanceHapticState ?.filter { it != KeyguardQuickAffordanceHapticViewModel.HapticState .NO_HAPTICS } ?.collect { state -> when (state) { KeyguardQuickAffordanceHapticViewModel.HapticState .TOGGLE_ON -> msdlPlayer.playToken(MSDLToken.SWITCH_ON) KeyguardQuickAffordanceHapticViewModel.HapticState .TOGGLE_OFF -> msdlPlayer.playToken(MSDLToken.SWITCH_OFF) KeyguardQuickAffordanceHapticViewModel.HapticState.LAUNCH -> msdlPlayer.playToken(MSDLToken.LONG_PRESS) KeyguardQuickAffordanceHapticViewModel.HapticState .NO_HAPTICS -> Unit } hapticsViewModel.resetLaunchingFromTriggeredResult() } } } } } Loading @@ -170,8 +136,10 @@ constructor( private fun updateButton( view: ImageView, viewModel: KeyguardQuickAffordanceViewModel, hapticsViewModel: KeyguardQuickAffordanceHapticViewModel, messageDisplayer: (Int) -> Unit, ) { hapticsViewModel.updateActivatedHistory(viewModel.isActivated) logger.logUpdate(viewModel) if (!viewModel.isVisible) { view.isInvisible = true Loading Loading @@ -263,16 +231,15 @@ constructor( shakeAnimator.doOnEnd { view.translationX = 0f } shakeAnimator.start() vibratorHelper?.playFeedback(KeyguardBottomAreaVibrations.Shake, msdlPlayer) hapticsViewModel.onQuickAffordanceClick() logger.logQuickAffordanceTapped(viewModel.configKey) } view.onLongClickListener = OnLongClickListener( falsingManager, viewModel, vibratorHelper, hapticsViewModel, onTouchListener, msdlPlayer, ) } else { view.setOnClickListener(OnClickListener(viewModel, checkNotNull(falsingManager))) Loading Loading @@ -332,9 +299,8 @@ constructor( private class OnLongClickListener( private val falsingManager: FalsingManager?, private val viewModel: KeyguardQuickAffordanceViewModel, private val vibratorHelper: VibratorHelper?, private val hapticsViewModel: KeyguardQuickAffordanceHapticViewModel, private val onTouchListener: KeyguardQuickAffordanceOnTouchListener, private val msdlPlayer: MSDLPlayer, ) : View.OnLongClickListener { override fun onLongClick(view: View): Boolean { if (falsingManager?.isFalseLongTap(FalsingManager.MODERATE_PENALTY) == true) { Loading @@ -342,6 +308,7 @@ constructor( } if (viewModel.configKey != null) { hapticsViewModel.onQuickAffordanceLongPress() viewModel.onClicked( KeyguardQuickAffordanceViewModel.OnClickedParameters( configKey = viewModel.configKey, Loading @@ -349,14 +316,6 @@ constructor( slotId = viewModel.slotId, ) ) vibratorHelper?.playFeedback( if (viewModel.isActivated) { KeyguardBottomAreaVibrations.Activated } else { KeyguardBottomAreaVibrations.Deactivated }, msdlPlayer, ) } onTouchListener.cancel() Loading @@ -368,13 +327,3 @@ constructor( private data class ConfigurationBasedDimensions(val buttonSizePx: Size) } private fun VibratorHelper.playFeedback(effect: VibrationEffect, msdlPlayer: MSDLPlayer) { if (!Flags.msdlFeedback()) { vibrate(effect) } else { if (effect == KeyguardBottomAreaVibrations.Shake) { msdlPlayer.playToken(MSDLToken.FAILURE) } } }
packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordanceHapticViewModel.kt +43 −59 File changed.Preview size limit exceeded, changes collapsed. Show changes