diff options
author | 2024-09-30 15:36:00 +0000 | |
---|---|---|
committer | 2024-09-30 15:36:00 +0000 | |
commit | fc644a3383dca2e1e9fd8b14c4ebb2ca36c20b6f (patch) | |
tree | c7abf9a634d77f8a4612d26d0a621952bcb4e5f6 /packages | |
parent | 8f58d8e262b77f3006a2307083d131b25348aa81 (diff) | |
parent | 6e7adcf3c5ffa38e0206ff56cc0ae65d6131d4ac (diff) |
Merge "Adding MSDL haptic feedback when pulling down the shade." into main
Diffstat (limited to 'packages')
9 files changed, 393 insertions, 64 deletions
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/OverlayShade.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/OverlayShade.kt index 4162891c0e0b..6f1349f20e4d 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/OverlayShade.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/OverlayShade.kt @@ -18,7 +18,6 @@ package com.android.systemui.shade.ui.composable -import android.view.HapticFeedbackConstants import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Box @@ -40,20 +39,17 @@ import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.MaterialTheme import androidx.compose.material3.windowsizeclass.WindowWidthSizeClass import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.ReadOnlyComposable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalLayoutDirection -import androidx.compose.ui.platform.LocalView import androidx.compose.ui.unit.dp import com.android.compose.animation.scene.ElementKey import com.android.compose.animation.scene.LowestZIndexContentPicker import com.android.compose.animation.scene.SceneScope import com.android.compose.windowsizeclass.LocalWindowSizeClass -import com.android.systemui.scene.shared.model.Scenes /** Renders a lightweight shade UI container, as an overlay. */ @Composable @@ -62,13 +58,6 @@ fun SceneScope.OverlayShade( modifier: Modifier = Modifier, content: @Composable () -> Unit, ) { - val view = LocalView.current - LaunchedEffect(Unit) { - if (layoutState.currentTransition?.fromContent == Scenes.Gone) { - view.performHapticFeedback(HapticFeedbackConstants.GESTURE_START) - } - } - Box(modifier) { Scrim(onClicked = onScrimClicked) diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt index db0fe3e3f79d..ef415b151200 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt @@ -16,7 +16,6 @@ package com.android.systemui.shade.ui.composable -import android.view.HapticFeedbackConstants import android.view.ViewGroup import androidx.compose.animation.core.animateDpAsState import androidx.compose.animation.core.animateFloatAsState @@ -60,7 +59,6 @@ import androidx.compose.ui.layout.layoutId import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalLifecycleOwner -import androidx.compose.ui.platform.LocalView import androidx.compose.ui.res.colorResource import androidx.compose.ui.res.dimensionResource import androidx.compose.ui.unit.dp @@ -226,12 +224,6 @@ private fun SceneScope.ShadeScene( shadeSession: SaveableSession, usingCollapsedLandscapeMedia: Boolean, ) { - val view = LocalView.current - LaunchedEffect(Unit) { - if (layoutState.currentTransition?.fromContent == Scenes.Gone) { - view.performHapticFeedback(HapticFeedbackConstants.GESTURE_START) - } - } val shadeMode by viewModel.shadeMode.collectAsStateWithLifecycle() when (shadeMode) { diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerHapticsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerHapticsViewModelTest.kt new file mode 100644 index 000000000000..664315d19494 --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerHapticsViewModelTest.kt @@ -0,0 +1,233 @@ +/* + * 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.scene.ui.viewmodel + +import android.platform.test.annotations.DisableFlags +import android.platform.test.annotations.EnableFlags +import android.view.HapticFeedbackConstants +import android.view.View +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.compose.animation.scene.ContentKey +import com.android.compose.animation.scene.ObservableTransitionState +import com.android.compose.animation.scene.ObservableTransitionState.Transition.ShowOrHideOverlay +import com.android.compose.animation.scene.OverlayKey +import com.android.compose.animation.scene.SceneKey +import com.android.systemui.Flags +import com.android.systemui.SysuiTestCase +import com.android.systemui.flags.EnableSceneContainer +import com.android.systemui.haptics.msdl.fakeMSDLPlayer +import com.android.systemui.kosmos.testScope +import com.android.systemui.lifecycle.activateIn +import com.android.systemui.scene.domain.interactor.sceneInteractor +import com.android.systemui.scene.sceneContainerHapticsViewModelFactory +import com.android.systemui.scene.shared.model.Overlays +import com.android.systemui.scene.shared.model.Scenes +import com.android.systemui.testKosmos +import com.google.android.msdl.data.model.MSDLToken +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.kotlin.eq +import org.mockito.kotlin.mock +import org.mockito.kotlin.verify +import org.mockito.kotlin.verifyZeroInteractions + +@OptIn(ExperimentalCoroutinesApi::class) +@SmallTest +@RunWith(AndroidJUnit4::class) +@EnableSceneContainer +class SceneContainerHapticsViewModelTest() : SysuiTestCase() { + + private val kosmos = testKosmos() + private val testScope = kosmos.testScope + private val sceneInteractor by lazy { kosmos.sceneInteractor } + private val msdlPlayer = kosmos.fakeMSDLPlayer + private val view = mock<View>() + + private lateinit var underTest: SceneContainerHapticsViewModel + + @Before + fun setup() { + underTest = kosmos.sceneContainerHapticsViewModelFactory.create(view) + underTest.activateIn(testScope) + } + + @EnableFlags(Flags.FLAG_MSDL_FEEDBACK) + @DisableFlags(Flags.FLAG_DUAL_SHADE) + @Test + fun onValidSceneTransition_withMSDL_playsMSDLShadePullHaptics() = + testScope.runTest { + // GIVEN a valid scene transition to play haptics + val validTransition = createTransitionState(from = Scenes.Gone, to = Scenes.Shade) + + // WHEN the transition occurs + sceneInteractor.setTransitionState(MutableStateFlow(validTransition)) + runCurrent() + + // THEN the expected token plays without interaction properties + assertThat(msdlPlayer.latestTokenPlayed).isEqualTo(MSDLToken.SWIPE_THRESHOLD_INDICATOR) + assertThat(msdlPlayer.latestPropertiesPlayed).isNull() + } + + @EnableFlags(Flags.FLAG_MSDL_FEEDBACK) + @DisableFlags(Flags.FLAG_DUAL_SHADE) + @Test + fun onInValidSceneTransition_withMSDL_doesNotPlayMSDLShadePullHaptics() = + testScope.runTest { + // GIVEN an invalid scene transition to play haptics + val invalidTransition = createTransitionState(from = Scenes.Shade, to = Scenes.Gone) + + // WHEN the transition occurs + sceneInteractor.setTransitionState(MutableStateFlow(invalidTransition)) + runCurrent() + + // THEN the no token plays with no interaction properties + assertThat(msdlPlayer.latestTokenPlayed).isNull() + assertThat(msdlPlayer.latestPropertiesPlayed).isNull() + } + + @DisableFlags(Flags.FLAG_DUAL_SHADE, Flags.FLAG_MSDL_FEEDBACK) + @Test + fun onValidSceneTransition_withoutMSDL_playsHapticConstantForShadePullHaptics() = + testScope.runTest { + // GIVEN a valid scene transition to play haptics + val validTransition = createTransitionState(from = Scenes.Gone, to = Scenes.Shade) + + // WHEN the transition occurs + sceneInteractor.setTransitionState(MutableStateFlow(validTransition)) + runCurrent() + + // THEN the expected haptic feedback constant plays + verify(view).performHapticFeedback(eq(HapticFeedbackConstants.GESTURE_START)) + } + + @DisableFlags(Flags.FLAG_DUAL_SHADE, Flags.FLAG_MSDL_FEEDBACK) + @Test + fun onInValidSceneTransition_withoutMSDL_doesNotPlayHapticConstantForShadePullHaptics() = + testScope.runTest { + // GIVEN an invalid scene transition to play haptics + val invalidTransition = createTransitionState(from = Scenes.Shade, to = Scenes.Gone) + + // WHEN the transition occurs + sceneInteractor.setTransitionState(MutableStateFlow(invalidTransition)) + runCurrent() + + // THEN the view does not play a haptic feedback constant + verifyZeroInteractions(view) + } + + @EnableFlags(Flags.FLAG_MSDL_FEEDBACK, Flags.FLAG_DUAL_SHADE) + @Test + fun onValidOverlayTransition_withMSDL_playsMSDLShadePullHaptics() = + testScope.runTest { + // GIVEN a valid scene transition to play haptics + val validTransition = + createTransitionState(from = Scenes.Gone, to = Overlays.NotificationsShade) + + // WHEN the transition occurs + sceneInteractor.setTransitionState(MutableStateFlow(validTransition)) + runCurrent() + + // THEN the expected token plays without interaction properties + assertThat(msdlPlayer.latestTokenPlayed).isEqualTo(MSDLToken.SWIPE_THRESHOLD_INDICATOR) + assertThat(msdlPlayer.latestPropertiesPlayed).isNull() + } + + @EnableFlags(Flags.FLAG_MSDL_FEEDBACK, Flags.FLAG_DUAL_SHADE) + @Test + fun onInValidOverlayTransition_withMSDL_doesNotPlayMSDLShadePullHaptics() = + testScope.runTest { + // GIVEN an invalid scene transition to play haptics + val invalidTransition = + createTransitionState(from = Scenes.Bouncer, to = Overlays.NotificationsShade) + + // WHEN the transition occurs + sceneInteractor.setTransitionState(MutableStateFlow(invalidTransition)) + runCurrent() + + // THEN the no token plays with no interaction properties + assertThat(msdlPlayer.latestTokenPlayed).isNull() + assertThat(msdlPlayer.latestPropertiesPlayed).isNull() + } + + @EnableFlags(Flags.FLAG_DUAL_SHADE) + @DisableFlags(Flags.FLAG_MSDL_FEEDBACK) + @Test + fun onValidOverlayTransition_withoutMSDL_playsHapticConstantForShadePullHaptics() = + testScope.runTest { + // GIVEN a valid scene transition to play haptics + val validTransition = + createTransitionState(from = Scenes.Gone, to = Overlays.NotificationsShade) + + // WHEN the transition occurs + sceneInteractor.setTransitionState(MutableStateFlow(validTransition)) + runCurrent() + + // THEN the expected haptic feedback constant plays + verify(view).performHapticFeedback(eq(HapticFeedbackConstants.GESTURE_START)) + } + + @EnableFlags(Flags.FLAG_DUAL_SHADE) + @DisableFlags(Flags.FLAG_MSDL_FEEDBACK) + @Test + fun onInValidOverlayTransition_withoutMSDL_doesNotPlayHapticConstantForShadePullHaptics() = + testScope.runTest { + // GIVEN an invalid scene transition to play haptics + val invalidTransition = + createTransitionState(from = Scenes.Bouncer, to = Overlays.NotificationsShade) + + // WHEN the transition occurs + sceneInteractor.setTransitionState(MutableStateFlow(invalidTransition)) + runCurrent() + + // THEN the view does not play a haptic feedback constant + verifyZeroInteractions(view) + } + + private fun createTransitionState(from: SceneKey, to: ContentKey) = + when (to) { + is SceneKey -> + ObservableTransitionState.Transition( + fromScene = from, + toScene = to, + currentScene = flowOf(from), + progress = MutableStateFlow(0.2f), + isInitiatedByUserInput = true, + isUserInputOngoing = flowOf(true), + ) + is OverlayKey -> + ShowOrHideOverlay( + overlay = to, + fromContent = from, + toContent = to, + currentScene = from, + currentOverlays = sceneInteractor.currentOverlays, + progress = MutableStateFlow(0.2f), + isInitiatedByUserInput = true, + isUserInputOngoing = flowOf(true), + previewProgress = flowOf(0f), + isInPreviewStage = flowOf(false), + ) + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt index e60e742e9917..a37f511cef69 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt @@ -21,23 +21,21 @@ package com.android.systemui.scene.ui.viewmodel import android.platform.test.annotations.DisableFlags import android.platform.test.annotations.EnableFlags import android.view.MotionEvent +import android.view.View import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.compose.animation.scene.DefaultEdgeDetector import com.android.systemui.SysuiTestCase -import com.android.systemui.classifier.domain.interactor.falsingInteractor import com.android.systemui.classifier.fakeFalsingManager import com.android.systemui.coroutines.collectLastValue import com.android.systemui.flags.EnableSceneContainer import com.android.systemui.kosmos.testScope import com.android.systemui.lifecycle.activateIn import com.android.systemui.power.data.repository.fakePowerRepository -import com.android.systemui.power.domain.interactor.powerInteractor import com.android.systemui.scene.domain.interactor.sceneInteractor import com.android.systemui.scene.fakeOverlaysByKeys import com.android.systemui.scene.sceneContainerConfig -import com.android.systemui.scene.sceneContainerGestureFilterFactory -import com.android.systemui.scene.shared.logger.sceneLogger +import com.android.systemui.scene.sceneContainerViewModelFactory import com.android.systemui.scene.shared.model.Overlays import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.scene.shared.model.fakeSceneDataSource @@ -72,6 +70,7 @@ class SceneContainerViewModelTest : SysuiTestCase() { private val fakeShadeRepository by lazy { kosmos.fakeShadeRepository } private val sceneContainerConfig by lazy { kosmos.sceneContainerConfig } private val falsingManager by lazy { kosmos.fakeFalsingManager } + private val view = mock<View>() private lateinit var underTest: SceneContainerViewModel @@ -81,16 +80,10 @@ class SceneContainerViewModelTest : SysuiTestCase() { @Before fun setUp() { underTest = - SceneContainerViewModel( - sceneInteractor = sceneInteractor, - falsingInteractor = kosmos.falsingInteractor, - powerInteractor = kosmos.powerInteractor, - shadeInteractor = kosmos.shadeInteractor, - splitEdgeDetector = kosmos.splitEdgeDetector, - logger = kosmos.sceneLogger, - gestureFilterFactory = kosmos.sceneContainerGestureFilterFactory, - displayId = kosmos.displayTracker.defaultDisplayId, - motionEventHandlerReceiver = { motionEventHandler -> + kosmos.sceneContainerViewModelFactory.create( + view, + kosmos.displayTracker.defaultDisplayId, + { motionEventHandler -> this@SceneContainerViewModelTest.motionEventHandler = motionEventHandler }, ) diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt index a7e7d8bb34dc..a8be5804d04a 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt @@ -108,7 +108,11 @@ object SceneWindowRootViewBinder { traceName = "SceneWindowRootViewBinder", minWindowLifecycleState = WindowLifecycleState.ATTACHED, factory = { - viewModelFactory.create(view.context.displayId, motionEventHandlerReceiver) + viewModelFactory.create( + view, + view.context.displayId, + motionEventHandlerReceiver, + ) }, ) { viewModel -> try { diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerHapticsViewModel.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerHapticsViewModel.kt new file mode 100644 index 000000000000..4ef8e0fc3167 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerHapticsViewModel.kt @@ -0,0 +1,90 @@ +/* + * 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.scene.ui.viewmodel + +import android.view.HapticFeedbackConstants +import android.view.View +import com.android.compose.animation.scene.ObservableTransitionState +import com.android.systemui.Flags +import com.android.systemui.lifecycle.ExclusiveActivatable +import com.android.systemui.scene.domain.interactor.SceneInteractor +import com.android.systemui.scene.shared.model.Overlays +import com.android.systemui.scene.shared.model.Scenes +import com.android.systemui.shade.domain.interactor.ShadeInteractor +import com.google.android.msdl.data.model.MSDLToken +import com.google.android.msdl.domain.MSDLPlayer +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject +import kotlinx.coroutines.awaitCancellation +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.distinctUntilChanged + +/** + * Models haptics UI state for the scene container. + * + * This model gets a [View] to play haptics using the [View.performHapticFeedback] API. This should + * be the only purpose of this reference. + */ +class SceneContainerHapticsViewModel +@AssistedInject +constructor( + @Assisted private val view: View, + sceneInteractor: SceneInteractor, + shadeInteractor: ShadeInteractor, + private val msdlPlayer: MSDLPlayer, +) : ExclusiveActivatable() { + + /** Should haptics be played by pulling down the shade */ + private val isShadePullHapticsRequired: Flow<Boolean> = + combine(shadeInteractor.isUserInteracting, sceneInteractor.transitionState) { + interacting, + transitionState -> + interacting && transitionState.isValidForShadePullHaptics() + } + .distinctUntilChanged() + + override suspend fun onActivated(): Nothing { + isShadePullHapticsRequired.collect { playShadePullHaptics -> + if (!playShadePullHaptics) return@collect + + if (Flags.msdlFeedback()) { + msdlPlayer.playToken(MSDLToken.SWIPE_THRESHOLD_INDICATOR) + } else { + view.performHapticFeedback(HapticFeedbackConstants.GESTURE_START) + } + } + awaitCancellation() + } + + private fun ObservableTransitionState.isValidForShadePullHaptics(): Boolean { + val validOrigin = + isTransitioning(from = Scenes.Gone) || isTransitioning(from = Scenes.Lockscreen) + val validDestination = + isTransitioning(to = Scenes.Shade) || + isTransitioning(to = Scenes.QuickSettings) || + isTransitioning(to = Overlays.QuickSettingsShade) || + isTransitioning(to = Overlays.NotificationsShade) + return validOrigin && validDestination + } + + @AssistedFactory + interface Factory { + fun create(view: View): SceneContainerHapticsViewModel + } +} diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt index 0bf2d499721b..f5053853846c 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt @@ -17,8 +17,10 @@ package com.android.systemui.scene.ui.viewmodel import android.view.MotionEvent +import android.view.View import androidx.compose.runtime.getValue import androidx.compose.ui.geometry.Offset +import com.android.app.tracing.coroutines.launch import com.android.compose.animation.scene.ContentKey import com.android.compose.animation.scene.DefaultEdgeDetector import com.android.compose.animation.scene.ObservableTransitionState @@ -60,6 +62,8 @@ constructor( private val splitEdgeDetector: SplitEdgeDetector, private val logger: SceneLogger, gestureFilterFactory: SceneContainerGestureFilter.Factory, + hapticsViewModelFactory: SceneContainerHapticsViewModel.Factory, + @Assisted view: View, @Assisted displayId: Int, @Assisted private val motionEventHandlerReceiver: (MotionEventHandler?) -> Unit, ) : ExclusiveActivatable() { @@ -72,6 +76,8 @@ constructor( /** Whether the container is visible. */ val isVisible: Boolean by hydrator.hydratedStateOf("isVisible", sceneInteractor.isVisible) + private val hapticsViewModel = hapticsViewModelFactory.create(view) + /** * The [SwipeSourceDetector] to use for defining which edges of the screen can be defined in the * [UserAction]s for this container. @@ -107,6 +113,7 @@ constructor( coroutineScope { launch { hydrator.activate() } launch { gestureFilter.activate() } + launch("SceneContainerHapticsViewModel") { hapticsViewModel.activate() } } awaitCancellation() } finally { @@ -281,6 +288,7 @@ constructor( @AssistedFactory interface Factory { fun create( + view: View, displayId: Int, motionEventHandlerReceiver: (MotionEventHandler?) -> Unit, ): SceneContainerViewModel diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/composable/BouncerPredictiveBackTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/composable/BouncerPredictiveBackTest.kt index 5cc64547aa6b..0d369a3ea80c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/composable/BouncerPredictiveBackTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/composable/BouncerPredictiveBackTest.kt @@ -19,6 +19,7 @@ package com.android.systemui.bouncer.ui.composable import android.app.AlertDialog import android.platform.test.annotations.MotionTest import android.testing.TestableLooper.RunWithLooper +import android.view.View import androidx.activity.BackEventCompat import androidx.compose.animation.core.Animatable import androidx.compose.animation.core.tween @@ -52,27 +53,21 @@ import com.android.systemui.bouncer.ui.BouncerDialogFactory import com.android.systemui.bouncer.ui.viewmodel.BouncerSceneContentViewModel import com.android.systemui.bouncer.ui.viewmodel.BouncerUserActionsViewModel import com.android.systemui.bouncer.ui.viewmodel.bouncerSceneContentViewModel -import com.android.systemui.classifier.domain.interactor.falsingInteractor import com.android.systemui.flags.EnableSceneContainer import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.Kosmos.Fixture import com.android.systemui.lifecycle.ExclusiveActivatable import com.android.systemui.lifecycle.rememberViewModel import com.android.systemui.motion.createSysUiComposeMotionTestRule -import com.android.systemui.power.domain.interactor.powerInteractor import com.android.systemui.scene.domain.interactor.sceneInteractor import com.android.systemui.scene.domain.startable.sceneContainerStartable -import com.android.systemui.scene.sceneContainerGestureFilterFactory -import com.android.systemui.scene.shared.logger.sceneLogger +import com.android.systemui.scene.sceneContainerViewModelFactory import com.android.systemui.scene.shared.model.SceneContainerConfig import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.scene.shared.model.sceneDataSourceDelegator import com.android.systemui.scene.ui.composable.Scene import com.android.systemui.scene.ui.composable.SceneContainer -import com.android.systemui.scene.ui.viewmodel.SceneContainerViewModel -import com.android.systemui.scene.ui.viewmodel.splitEdgeDetector import com.android.systemui.settings.displayTracker -import com.android.systemui.shade.domain.interactor.shadeInteractor import com.android.systemui.testKosmos import kotlin.time.Duration.Companion.seconds import kotlinx.coroutines.awaitCancellation @@ -85,6 +80,7 @@ import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.mockito.MockitoAnnotations +import org.mockito.kotlin.mock import platform.test.motion.compose.ComposeFeatureCaptures.positionInRoot import platform.test.motion.compose.ComposeRecordingSpec import platform.test.motion.compose.MotionControl @@ -121,24 +117,17 @@ class BouncerPredictiveBackTest : SysuiTestCase() { val navigationDistances = mapOf(Scenes.Lockscreen to 1, Scenes.Bouncer to 0) SceneContainerConfig(sceneKeys, initialSceneKey, emptyList(), navigationDistances) } + private val view = mock<View>() private val transitionState by lazy { MutableStateFlow<ObservableTransitionState>( ObservableTransitionState.Idle(kosmos.sceneContainerConfig.initialSceneKey) ) } + private val sceneContainerViewModel by lazy { - SceneContainerViewModel( - sceneInteractor = kosmos.sceneInteractor, - falsingInteractor = kosmos.falsingInteractor, - powerInteractor = kosmos.powerInteractor, - shadeInteractor = kosmos.shadeInteractor, - splitEdgeDetector = kosmos.splitEdgeDetector, - logger = kosmos.sceneLogger, - gestureFilterFactory = kosmos.sceneContainerGestureFilterFactory, - displayId = kosmos.displayTracker.defaultDisplayId, - motionEventHandlerReceiver = {}, - ) + kosmos.sceneContainerViewModelFactory + .create(view, kosmos.displayTracker.defaultDisplayId, {}) .apply { setTransitionState(transitionState) } } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneKosmos.kt index 737aaf22b557..f842db4c0026 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneKosmos.kt @@ -1,7 +1,9 @@ package com.android.systemui.scene +import android.view.View import com.android.compose.animation.scene.ObservableTransitionState import com.android.systemui.classifier.domain.interactor.falsingInteractor +import com.android.systemui.haptics.msdl.msdlPlayer import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.Kosmos.Fixture import com.android.systemui.power.domain.interactor.powerInteractor @@ -13,11 +15,13 @@ import com.android.systemui.scene.shared.model.SceneContainerConfig import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.scene.ui.FakeOverlay import com.android.systemui.scene.ui.viewmodel.SceneContainerGestureFilter +import com.android.systemui.scene.ui.viewmodel.SceneContainerHapticsViewModel import com.android.systemui.scene.ui.viewmodel.SceneContainerViewModel import com.android.systemui.scene.ui.viewmodel.splitEdgeDetector import com.android.systemui.settings.displayTracker import com.android.systemui.shade.domain.interactor.shadeInteractor import kotlinx.coroutines.flow.MutableStateFlow +import org.mockito.kotlin.mock var Kosmos.sceneKeys by Fixture { listOf( @@ -68,18 +72,32 @@ val Kosmos.transitionState by Fixture { } val Kosmos.sceneContainerViewModel by Fixture { - SceneContainerViewModel( - sceneInteractor = sceneInteractor, - falsingInteractor = falsingInteractor, - powerInteractor = powerInteractor, - shadeInteractor = shadeInteractor, - splitEdgeDetector = splitEdgeDetector, - gestureFilterFactory = sceneContainerGestureFilterFactory, - displayId = displayTracker.defaultDisplayId, - motionEventHandlerReceiver = {}, - logger = sceneLogger, - ) - .apply { setTransitionState(transitionState) } + sceneContainerViewModelFactory.create(mock<View>(), displayTracker.defaultDisplayId, {}).apply { + setTransitionState(transitionState) + } +} + +val Kosmos.sceneContainerViewModelFactory by Fixture { + object : SceneContainerViewModel.Factory { + override fun create( + view: View, + displayId: Int, + motionEventHandlerReceiver: (SceneContainerViewModel.MotionEventHandler?) -> Unit, + ): SceneContainerViewModel = + SceneContainerViewModel( + sceneInteractor = sceneInteractor, + falsingInteractor = falsingInteractor, + powerInteractor = powerInteractor, + shadeInteractor = shadeInteractor, + splitEdgeDetector = splitEdgeDetector, + logger = sceneLogger, + gestureFilterFactory = sceneContainerGestureFilterFactory, + hapticsViewModelFactory = sceneContainerHapticsViewModelFactory, + view = view, + displayId = displayId, + motionEventHandlerReceiver = motionEventHandlerReceiver, + ) + } } val Kosmos.sceneContainerGestureFilterFactory by Fixture { @@ -92,3 +110,16 @@ val Kosmos.sceneContainerGestureFilterFactory by Fixture { } } } + +val Kosmos.sceneContainerHapticsViewModelFactory by Fixture { + object : SceneContainerHapticsViewModel.Factory { + override fun create(view: View): SceneContainerHapticsViewModel { + return SceneContainerHapticsViewModel( + view = view, + sceneInteractor = sceneInteractor, + shadeInteractor = shadeInteractor, + msdlPlayer = msdlPlayer, + ) + } + } +} |