summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt39
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/composable/BouncerPredictiveBackTest.kt2
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/view/SceneJankMonitorTest.kt206
-rw-r--r--packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneJankMonitor.kt124
-rw-r--r--packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootView.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt3
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/scene/ui/view/SceneJankMonitorKosmos.kt35
8 files changed, 408 insertions, 7 deletions
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt
index c3dc84d0a12c..a6a63624cf7c 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt
@@ -33,6 +33,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.platform.LocalView
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.android.compose.animation.scene.ContentKey
import com.android.compose.animation.scene.MutableSceneTransitionLayoutState
@@ -43,11 +44,13 @@ import com.android.compose.animation.scene.SceneTransitions
import com.android.compose.animation.scene.UserAction
import com.android.compose.animation.scene.UserActionResult
import com.android.compose.animation.scene.observableTransitionState
+import com.android.systemui.lifecycle.rememberActivated
import com.android.systemui.qs.ui.adapter.QSSceneAdapter
import com.android.systemui.qs.ui.composable.QuickSettingsTheme
import com.android.systemui.ribbon.ui.composable.BottomRightCornerRibbon
import com.android.systemui.scene.shared.model.SceneDataSourceDelegator
import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.scene.ui.view.SceneJankMonitor
import com.android.systemui.scene.ui.viewmodel.SceneContainerViewModel
import javax.inject.Provider
@@ -82,16 +85,38 @@ fun SceneContainer(
sceneTransitions: SceneTransitions,
dataSourceDelegator: SceneDataSourceDelegator,
qsSceneAdapter: Provider<QSSceneAdapter>,
+ sceneJankMonitorFactory: SceneJankMonitor.Factory,
modifier: Modifier = Modifier,
) {
val coroutineScope = rememberCoroutineScope()
- val state: MutableSceneTransitionLayoutState = remember {
- MutableSceneTransitionLayoutState(
- initialScene = initialSceneKey,
- canChangeScene = { toScene -> viewModel.canChangeScene(toScene) },
- transitions = sceneTransitions,
- )
- }
+
+ val view = LocalView.current
+ val sceneJankMonitor =
+ rememberActivated(traceName = "sceneJankMonitor") { sceneJankMonitorFactory.create() }
+
+ val state: MutableSceneTransitionLayoutState =
+ remember(view, sceneJankMonitor) {
+ MutableSceneTransitionLayoutState(
+ initialScene = initialSceneKey,
+ canChangeScene = { toScene -> viewModel.canChangeScene(toScene) },
+ transitions = sceneTransitions,
+ onTransitionStart = { transition ->
+ sceneJankMonitor.onTransitionStart(
+ view = view,
+ from = transition.fromContent,
+ to = transition.toContent,
+ cuj = transition.cuj,
+ )
+ },
+ onTransitionEnd = { transition ->
+ sceneJankMonitor.onTransitionEnd(
+ from = transition.fromContent,
+ to = transition.toContent,
+ cuj = transition.cuj,
+ )
+ },
+ )
+ }
DisposableEffect(state) {
val dataSource = SceneTransitionLayoutDataSource(state, coroutineScope)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/composable/BouncerPredictiveBackTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/composable/BouncerPredictiveBackTest.kt
index b33a83cf202a..a65415509d38 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/composable/BouncerPredictiveBackTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/composable/BouncerPredictiveBackTest.kt
@@ -69,6 +69,7 @@ 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.composable.SceneContainerTransitions
+import com.android.systemui.scene.ui.view.sceneJankMonitorFactory
import com.android.systemui.testKosmos
import kotlin.time.Duration.Companion.seconds
import kotlinx.coroutines.awaitCancellation
@@ -193,6 +194,7 @@ class BouncerPredictiveBackTest : SysuiTestCase() {
overlayByKey = emptyMap(),
dataSourceDelegator = kosmos.sceneDataSourceDelegator,
qsSceneAdapter = { kosmos.fakeQsSceneAdapter },
+ sceneJankMonitorFactory = kosmos.sceneJankMonitorFactory,
)
}
},
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/view/SceneJankMonitorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/view/SceneJankMonitorTest.kt
new file mode 100644
index 000000000000..19369e7b343d
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/view/SceneJankMonitorTest.kt
@@ -0,0 +1,206 @@
+/*
+ * Copyright (C) 2025 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.view
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.internal.jank.Cuj
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository
+import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
+import com.android.systemui.deviceentry.domain.interactor.deviceUnlockedInteractor
+import com.android.systemui.jank.interactionJankMonitor
+import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFingerprintAuthRepository
+import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.runCurrent
+import com.android.systemui.kosmos.runTestWithSnapshots
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.lifecycle.activateIn
+import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.kotlin.any
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.never
+import org.mockito.kotlin.times
+import org.mockito.kotlin.verify
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class SceneJankMonitorTest : SysuiTestCase() {
+
+ private val kosmos = testKosmos()
+ private val underTest: SceneJankMonitor = kosmos.sceneJankMonitorFactory.create()
+
+ @Before
+ fun setUp() {
+ underTest.activateIn(kosmos.testScope)
+ }
+
+ @Test
+ fun onTransitionStart_withProvidedCuj_beginsThatCuj() =
+ kosmos.runTestWithSnapshots {
+ val cuj = 1337
+ underTest.onTransitionStart(
+ view = mock(),
+ from = Scenes.Communal,
+ to = Scenes.Dream,
+ cuj = cuj,
+ )
+ verify(interactionJankMonitor).begin(any(), eq(cuj))
+ verify(interactionJankMonitor, never()).end(anyInt())
+ }
+
+ @Test
+ fun onTransitionEnd_withProvidedCuj_endsThatCuj() =
+ kosmos.runTestWithSnapshots {
+ val cuj = 1337
+ underTest.onTransitionEnd(from = Scenes.Communal, to = Scenes.Dream, cuj = cuj)
+ verify(interactionJankMonitor, never()).begin(any(), anyInt())
+ verify(interactionJankMonitor).end(cuj)
+ }
+
+ @Test
+ fun bouncer_authMethodPin() =
+ kosmos.runTestWithSnapshots {
+ bouncer(
+ authenticationMethod = AuthenticationMethodModel.Pin,
+ appearCuj = Cuj.CUJ_LOCKSCREEN_PIN_APPEAR,
+ disappearCuj = Cuj.CUJ_LOCKSCREEN_PIN_DISAPPEAR,
+ )
+ }
+
+ @Test
+ fun bouncer_authMethodSim() =
+ kosmos.runTestWithSnapshots {
+ bouncer(
+ authenticationMethod = AuthenticationMethodModel.Sim,
+ appearCuj = Cuj.CUJ_LOCKSCREEN_PIN_APPEAR,
+ disappearCuj = Cuj.CUJ_LOCKSCREEN_PIN_DISAPPEAR,
+ // When the auth method is SIM, unlocking doesn't work like normal. Instead of
+ // leaving the bouncer, the bouncer is switched over to the real authentication
+ // method when the SIM is unlocked.
+ //
+ // Therefore, there's no point in testing this code path and it will, in fact, fail
+ // to unlock.
+ testUnlockedDisappearance = false,
+ )
+ }
+
+ @Test
+ fun bouncer_authMethodPattern() =
+ kosmos.runTestWithSnapshots {
+ bouncer(
+ authenticationMethod = AuthenticationMethodModel.Pattern,
+ appearCuj = Cuj.CUJ_LOCKSCREEN_PATTERN_APPEAR,
+ disappearCuj = Cuj.CUJ_LOCKSCREEN_PATTERN_DISAPPEAR,
+ )
+ }
+
+ @Test
+ fun bouncer_authMethodPassword() =
+ kosmos.runTestWithSnapshots {
+ bouncer(
+ authenticationMethod = AuthenticationMethodModel.Password,
+ appearCuj = Cuj.CUJ_LOCKSCREEN_PASSWORD_APPEAR,
+ disappearCuj = Cuj.CUJ_LOCKSCREEN_PASSWORD_DISAPPEAR,
+ )
+ }
+
+ private fun Kosmos.bouncer(
+ authenticationMethod: AuthenticationMethodModel,
+ appearCuj: Int,
+ disappearCuj: Int,
+ testUnlockedDisappearance: Boolean = true,
+ ) {
+ // Set up state:
+ fakeAuthenticationRepository.setAuthenticationMethod(authenticationMethod)
+ runCurrent()
+
+ fun verifyCujCounts(
+ beginAppearCount: Int = 0,
+ beginDisappearCount: Int = 0,
+ endAppearCount: Int = 0,
+ endDisappearCount: Int = 0,
+ ) {
+ verify(interactionJankMonitor, times(beginAppearCount)).begin(any(), eq(appearCuj))
+ verify(interactionJankMonitor, times(beginDisappearCount))
+ .begin(any(), eq(disappearCuj))
+ verify(interactionJankMonitor, times(endAppearCount)).end(appearCuj)
+ verify(interactionJankMonitor, times(endDisappearCount)).end(disappearCuj)
+ }
+
+ // Precondition checks:
+ assertThat(deviceUnlockedInteractor.deviceUnlockStatus.value.isUnlocked).isFalse()
+ verifyCujCounts()
+
+ // Bouncer appears CUJ:
+ underTest.onTransitionStart(
+ view = mock(),
+ from = Scenes.Lockscreen,
+ to = Scenes.Bouncer,
+ cuj = null,
+ )
+ verifyCujCounts(beginAppearCount = 1)
+ underTest.onTransitionEnd(from = Scenes.Lockscreen, to = Scenes.Bouncer, cuj = null)
+ verifyCujCounts(beginAppearCount = 1, endAppearCount = 1)
+
+ // Bouncer disappear CUJ but it doesn't log because the device isn't unlocked.
+ underTest.onTransitionStart(
+ view = mock(),
+ from = Scenes.Bouncer,
+ to = Scenes.Lockscreen,
+ cuj = null,
+ )
+ verifyCujCounts(beginAppearCount = 1, endAppearCount = 1)
+ underTest.onTransitionEnd(from = Scenes.Bouncer, to = Scenes.Lockscreen, cuj = null)
+ verifyCujCounts(beginAppearCount = 1, endAppearCount = 1)
+
+ if (!testUnlockedDisappearance) {
+ return
+ }
+
+ // Unlock the device and transition away from the bouncer.
+ fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus(
+ SuccessFingerprintAuthenticationStatus(0, true)
+ )
+ runCurrent()
+ assertThat(deviceUnlockedInteractor.deviceUnlockStatus.value.isUnlocked).isTrue()
+
+ // Bouncer disappear CUJ and it doeslog because the device is unlocked.
+ underTest.onTransitionStart(
+ view = mock(),
+ from = Scenes.Bouncer,
+ to = Scenes.Gone,
+ cuj = null,
+ )
+ verifyCujCounts(beginAppearCount = 1, endAppearCount = 1, beginDisappearCount = 1)
+ underTest.onTransitionEnd(from = Scenes.Bouncer, to = Scenes.Gone, cuj = null)
+ verifyCujCounts(
+ beginAppearCount = 1,
+ endAppearCount = 1,
+ beginDisappearCount = 1,
+ endDisappearCount = 1,
+ )
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneJankMonitor.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneJankMonitor.kt
new file mode 100644
index 000000000000..48a49c60d8a2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneJankMonitor.kt
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2025 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.view
+
+import android.view.View
+import androidx.compose.runtime.getValue
+import com.android.compose.animation.scene.ContentKey
+import com.android.internal.jank.Cuj
+import com.android.internal.jank.Cuj.CujType
+import com.android.internal.jank.InteractionJankMonitor
+import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor
+import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
+import com.android.systemui.deviceentry.domain.interactor.DeviceUnlockedInteractor
+import com.android.systemui.lifecycle.ExclusiveActivatable
+import com.android.systemui.lifecycle.Hydrator
+import com.android.systemui.scene.shared.model.Scenes
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+
+/**
+ * Monitors scene transitions and reports the beginning and ending of each scene-related CUJ.
+ *
+ * This general-purpose monitor can be expanded to include other rules that respond to the beginning
+ * and/or ending of transitions and reports jank CUI markers to the [InteractionJankMonitor].
+ */
+class SceneJankMonitor
+@AssistedInject
+constructor(
+ authenticationInteractor: AuthenticationInteractor,
+ private val deviceUnlockedInteractor: DeviceUnlockedInteractor,
+ private val interactionJankMonitor: InteractionJankMonitor,
+) : ExclusiveActivatable() {
+
+ private val hydrator = Hydrator("SceneJankMonitor.hydrator")
+ private val authMethod: AuthenticationMethodModel? by
+ hydrator.hydratedStateOf(
+ traceName = "authMethod",
+ initialValue = null,
+ source = authenticationInteractor.authenticationMethod,
+ )
+
+ override suspend fun onActivated(): Nothing {
+ hydrator.activate()
+ }
+
+ /**
+ * Notifies that a transition is at its start.
+ *
+ * Should be called exactly once each time a new transition starts.
+ */
+ fun onTransitionStart(view: View, from: ContentKey, to: ContentKey, @CujType cuj: Int?) {
+ cuj.orCalculated(from, to) { nonNullCuj -> interactionJankMonitor.begin(view, nonNullCuj) }
+ }
+
+ /**
+ * Notifies that the previous transition is at its end.
+ *
+ * Should be called exactly once each time a transition ends.
+ */
+ fun onTransitionEnd(from: ContentKey, to: ContentKey, @CujType cuj: Int?) {
+ cuj.orCalculated(from, to) { nonNullCuj -> interactionJankMonitor.end(nonNullCuj) }
+ }
+
+ /**
+ * Returns this CUI marker (CUJ identifier), one that's calculated based on other state, or
+ * `null`, if no appropriate CUJ could be calculated.
+ */
+ private fun Int?.orCalculated(
+ from: ContentKey,
+ to: ContentKey,
+ ifNotNull: (nonNullCuj: Int) -> Unit,
+ ) {
+ val thisOrCalculatedCuj = this ?: calculatedCuj(from = from, to = to)
+
+ if (thisOrCalculatedCuj != null) {
+ ifNotNull(thisOrCalculatedCuj)
+ }
+ }
+
+ @CujType
+ private fun calculatedCuj(from: ContentKey, to: ContentKey): Int? {
+ val isDeviceUnlocked = deviceUnlockedInteractor.deviceUnlockStatus.value.isUnlocked
+ return when {
+ to == Scenes.Bouncer ->
+ when (authMethod) {
+ is AuthenticationMethodModel.Pin,
+ is AuthenticationMethodModel.Sim -> Cuj.CUJ_LOCKSCREEN_PIN_APPEAR
+ is AuthenticationMethodModel.Pattern -> Cuj.CUJ_LOCKSCREEN_PATTERN_APPEAR
+ is AuthenticationMethodModel.Password -> Cuj.CUJ_LOCKSCREEN_PASSWORD_APPEAR
+ is AuthenticationMethodModel.None -> null
+ null -> null
+ }
+ from == Scenes.Bouncer && isDeviceUnlocked ->
+ when (authMethod) {
+ is AuthenticationMethodModel.Pin,
+ is AuthenticationMethodModel.Sim -> Cuj.CUJ_LOCKSCREEN_PIN_DISAPPEAR
+ is AuthenticationMethodModel.Pattern -> Cuj.CUJ_LOCKSCREEN_PATTERN_DISAPPEAR
+ is AuthenticationMethodModel.Password -> Cuj.CUJ_LOCKSCREEN_PASSWORD_DISAPPEAR
+ is AuthenticationMethodModel.None -> null
+ null -> null
+ }
+ else -> null
+ }
+ }
+
+ @AssistedFactory
+ interface Factory {
+ fun create(): SceneJankMonitor
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootView.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootView.kt
index c45906840385..b8da2274eec1 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootView.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootView.kt
@@ -34,6 +34,7 @@ class SceneWindowRootView(context: Context, attrs: AttributeSet?) : WindowRootVi
layoutInsetController: LayoutInsetsController,
sceneDataSourceDelegator: SceneDataSourceDelegator,
qsSceneAdapter: Provider<QSSceneAdapter>,
+ sceneJankMonitorFactory: SceneJankMonitor.Factory,
) {
setLayoutInsetsController(layoutInsetController)
SceneWindowRootViewBinder.bind(
@@ -52,6 +53,7 @@ class SceneWindowRootView(context: Context, attrs: AttributeSet?) : WindowRootVi
},
dataSourceDelegator = sceneDataSourceDelegator,
qsSceneAdapter = qsSceneAdapter,
+ sceneJankMonitorFactory = sceneJankMonitorFactory,
)
}
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 f7061d9af961..7da007c2fe53 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
@@ -74,6 +74,7 @@ object SceneWindowRootViewBinder {
onVisibilityChangedInternal: (isVisible: Boolean) -> Unit,
dataSourceDelegator: SceneDataSourceDelegator,
qsSceneAdapter: Provider<QSSceneAdapter>,
+ sceneJankMonitorFactory: SceneJankMonitor.Factory,
) {
val unsortedSceneByKey: Map<SceneKey, Scene> = scenes.associateBy { scene -> scene.key }
val sortedSceneByKey: Map<SceneKey, Scene> =
@@ -133,6 +134,7 @@ object SceneWindowRootViewBinder {
dataSourceDelegator = dataSourceDelegator,
qsSceneAdapter = qsSceneAdapter,
containerConfig = containerConfig,
+ sceneJankMonitorFactory = sceneJankMonitorFactory,
)
.also { it.id = R.id.scene_container_root_composable }
)
@@ -169,6 +171,7 @@ object SceneWindowRootViewBinder {
dataSourceDelegator: SceneDataSourceDelegator,
qsSceneAdapter: Provider<QSSceneAdapter>,
containerConfig: SceneContainerConfig,
+ sceneJankMonitorFactory: SceneJankMonitor.Factory,
): View {
return ComposeView(context).apply {
setContent {
@@ -185,6 +188,7 @@ object SceneWindowRootViewBinder {
sceneTransitions = containerConfig.transitions,
dataSourceDelegator = dataSourceDelegator,
qsSceneAdapter = qsSceneAdapter,
+ sceneJankMonitorFactory = sceneJankMonitorFactory,
)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt
index e19112047d2a..3449e81a4630 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt
@@ -41,6 +41,7 @@ import com.android.systemui.scene.shared.model.SceneContainerConfig
import com.android.systemui.scene.shared.model.SceneDataSourceDelegator
import com.android.systemui.scene.ui.composable.Overlay
import com.android.systemui.scene.ui.composable.Scene
+import com.android.systemui.scene.ui.view.SceneJankMonitor
import com.android.systemui.scene.ui.view.SceneWindowRootView
import com.android.systemui.scene.ui.view.WindowRootView
import com.android.systemui.scene.ui.viewmodel.SceneContainerViewModel
@@ -89,6 +90,7 @@ abstract class ShadeViewProviderModule {
layoutInsetController: NotificationInsetsController,
sceneDataSourceDelegator: Provider<SceneDataSourceDelegator>,
qsSceneAdapter: Provider<QSSceneAdapter>,
+ sceneJankMonitorFactory: SceneJankMonitor.Factory,
): WindowRootView {
return if (SceneContainerFlag.isEnabled) {
checkNoSceneDuplicates(scenesProvider.get())
@@ -104,6 +106,7 @@ abstract class ShadeViewProviderModule {
layoutInsetController = layoutInsetController,
sceneDataSourceDelegator = sceneDataSourceDelegator.get(),
qsSceneAdapter = qsSceneAdapter,
+ sceneJankMonitorFactory = sceneJankMonitorFactory,
)
sceneWindowRootView
} else {
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/ui/view/SceneJankMonitorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/ui/view/SceneJankMonitorKosmos.kt
new file mode 100644
index 000000000000..bcba5ee50b8c
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/ui/view/SceneJankMonitorKosmos.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2025 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.view
+
+import com.android.systemui.authentication.domain.interactor.authenticationInteractor
+import com.android.systemui.deviceentry.domain.interactor.deviceUnlockedInteractor
+import com.android.systemui.jank.interactionJankMonitor
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+
+val Kosmos.sceneJankMonitorFactory: SceneJankMonitor.Factory by Fixture {
+ object : SceneJankMonitor.Factory {
+ override fun create(): SceneJankMonitor {
+ return SceneJankMonitor(
+ authenticationInteractor = authenticationInteractor,
+ deviceUnlockedInteractor = deviceUnlockedInteractor,
+ interactionJankMonitor = interactionJankMonitor,
+ )
+ }
+ }
+}