summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--packages/SystemUI/compose/facade/enabled/src/com/android/systemui/scene/LockscreenSceneModule.kt3
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenContent.kt43
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/shared/transition/FakeKeyguardTransitionAnimationCallbackTest.kt97
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelTest.kt290
-rw-r--r--packages/SystemUI/src/com/android/systemui/bouncer/dagger/BouncerLoggerModule.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageAuditLogger.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt22
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt9
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/shared/transition/KeyguardTransitionAnimationCallback.kt34
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/shared/transition/KeyguardTransitionAnimationCallbackDelegator.kt39
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModel.kt61
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt19
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryKosmos.kt8
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/shared/transition/FakeKeyguardTransitionAnimationCallback.kt55
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/shared/transition/KeyguardTransitionAnimationCallbackKosmos.kt32
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelKosmos.kt35
17 files changed, 552 insertions, 210 deletions
diff --git a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/scene/LockscreenSceneModule.kt b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/scene/LockscreenSceneModule.kt
index f490968b7a7c..8af131d975f1 100644
--- a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/scene/LockscreenSceneModule.kt
+++ b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/scene/LockscreenSceneModule.kt
@@ -17,6 +17,7 @@
package com.android.systemui.scene
import android.view.View
+import com.android.internal.jank.InteractionJankMonitor
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.KeyguardViewConfigurator
import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
@@ -62,12 +63,14 @@ interface LockscreenSceneModule {
notificationScrimViewModelFactory: NotificationLockscreenScrimViewModel.Factory,
blueprints: Set<@JvmSuppressWildcards ComposableLockscreenSceneBlueprint>,
clockInteractor: KeyguardClockInteractor,
+ interactionJankMonitor: InteractionJankMonitor,
): LockscreenContent {
return LockscreenContent(
viewModelFactory,
notificationScrimViewModelFactory,
blueprints,
clockInteractor,
+ interactionJankMonitor,
)
}
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenContent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenContent.kt
index 7b2f9dc76158..ba85f9570d09 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenContent.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenContent.kt
@@ -16,6 +16,7 @@
package com.android.systemui.keyguard.ui.composable
+import android.view.View
import androidx.compose.foundation.layout.Box
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
@@ -25,8 +26,13 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalView
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.android.compose.animation.scene.ContentScope
+import com.android.internal.jank.Cuj
+import com.android.internal.jank.Cuj.CujType
+import com.android.internal.jank.InteractionJankMonitor
import com.android.systemui.compose.modifiers.sysuiResTag
import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.transition.KeyguardTransitionAnimationCallback
import com.android.systemui.keyguard.ui.composable.blueprint.ComposableLockscreenSceneBlueprint
import com.android.systemui.keyguard.ui.viewmodel.LockscreenContentViewModel
import com.android.systemui.lifecycle.rememberViewModel
@@ -44,6 +50,7 @@ class LockscreenContent(
private val notificationScrimViewModelFactory: NotificationLockscreenScrimViewModel.Factory,
private val blueprints: Set<@JvmSuppressWildcards ComposableLockscreenSceneBlueprint>,
private val clockInteractor: KeyguardClockInteractor,
+ private val interactionJankMonitor: InteractionJankMonitor,
) {
private val blueprintByBlueprintId: Map<String, ComposableLockscreenSceneBlueprint> by lazy {
blueprints.associateBy { it.id }
@@ -51,8 +58,14 @@ class LockscreenContent(
@Composable
fun ContentScope.Content(modifier: Modifier = Modifier) {
+ val view = LocalView.current
val viewModel =
- rememberViewModel("LockscreenContent-viewModel") { viewModelFactory.create() }
+ rememberViewModel("LockscreenContent-viewModel") {
+ viewModelFactory.create(
+ keyguardTransitionAnimationCallback =
+ KeyguardTransitionAnimationCallbackImpl(view, interactionJankMonitor)
+ )
+ }
val notificationLockscreenScrimViewModel =
rememberViewModel("LockscreenContent-scrimViewModel") {
notificationScrimViewModelFactory.create()
@@ -69,7 +82,6 @@ class LockscreenContent(
val coroutineScope = rememberCoroutineScope()
val blueprintId by viewModel.blueprintId(coroutineScope).collectAsStateWithLifecycle()
- val view = LocalView.current
DisposableEffect(view) {
clockInteractor.clockEventController.registerListeners(view)
@@ -83,3 +95,30 @@ class LockscreenContent(
}
}
}
+
+private class KeyguardTransitionAnimationCallbackImpl(
+ private val view: View,
+ private val interactionJankMonitor: InteractionJankMonitor,
+) : KeyguardTransitionAnimationCallback {
+
+ override fun onAnimationStarted(from: KeyguardState, to: KeyguardState) {
+ cujOrNull(from, to)?.let { cuj -> interactionJankMonitor.begin(view, cuj) }
+ }
+
+ override fun onAnimationEnded(from: KeyguardState, to: KeyguardState) {
+ cujOrNull(from, to)?.let { cuj -> interactionJankMonitor.end(cuj) }
+ }
+
+ override fun onAnimationCanceled(from: KeyguardState, to: KeyguardState) {
+ cujOrNull(from, to)?.let { cuj -> interactionJankMonitor.cancel(cuj) }
+ }
+
+ @CujType
+ private fun cujOrNull(from: KeyguardState, to: KeyguardState): Int? {
+ return when {
+ from == KeyguardState.AOD -> Cuj.CUJ_LOCKSCREEN_TRANSITION_FROM_AOD
+ to == KeyguardState.AOD -> Cuj.CUJ_LOCKSCREEN_TRANSITION_TO_AOD
+ else -> null
+ }
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/shared/transition/FakeKeyguardTransitionAnimationCallbackTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/shared/transition/FakeKeyguardTransitionAnimationCallbackTest.kt
new file mode 100644
index 000000000000..6e44bc708e74
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/shared/transition/FakeKeyguardTransitionAnimationCallbackTest.kt
@@ -0,0 +1,97 @@
+/*
+ * 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.keyguard.shared.transition
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.kosmos.runTest
+import com.android.systemui.kosmos.useUnconfinedTestDispatcher
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class FakeKeyguardTransitionAnimationCallbackTest : SysuiTestCase() {
+
+ private val kosmos = testKosmos().useUnconfinedTestDispatcher()
+
+ val underTest = FakeKeyguardTransitionAnimationCallback()
+
+ @Test
+ fun onAnimationStarted() =
+ kosmos.runTest {
+ assertThat(underTest.activeAnimations).isEmpty()
+
+ underTest.onAnimationStarted(KeyguardState.LOCKSCREEN, KeyguardState.AOD)
+ assertThat(underTest.activeAnimations).hasSize(1)
+
+ underTest.onAnimationStarted(KeyguardState.AOD, KeyguardState.ALTERNATE_BOUNCER)
+ assertThat(underTest.activeAnimations).hasSize(2)
+ }
+
+ @Test
+ fun onAnimationEnded() =
+ kosmos.runTest {
+ underTest.onAnimationStarted(KeyguardState.LOCKSCREEN, KeyguardState.AOD)
+ underTest.onAnimationStarted(KeyguardState.AOD, KeyguardState.ALTERNATE_BOUNCER)
+ assertThat(underTest.activeAnimations).hasSize(2)
+
+ underTest.onAnimationEnded(KeyguardState.AOD, KeyguardState.ALTERNATE_BOUNCER)
+ assertThat(underTest.activeAnimations).hasSize(1)
+
+ underTest.onAnimationEnded(KeyguardState.LOCKSCREEN, KeyguardState.AOD)
+ assertThat(underTest.activeAnimations).isEmpty()
+ }
+
+ @Test
+ fun onAnimationCanceled() =
+ kosmos.runTest {
+ underTest.onAnimationStarted(KeyguardState.LOCKSCREEN, KeyguardState.AOD)
+ underTest.onAnimationStarted(KeyguardState.AOD, KeyguardState.ALTERNATE_BOUNCER)
+ assertThat(underTest.activeAnimations).hasSize(2)
+
+ underTest.onAnimationCanceled(KeyguardState.AOD, KeyguardState.ALTERNATE_BOUNCER)
+ assertThat(underTest.activeAnimations).hasSize(1)
+
+ underTest.onAnimationCanceled(KeyguardState.LOCKSCREEN, KeyguardState.AOD)
+ assertThat(underTest.activeAnimations).isEmpty()
+ }
+
+ @Test(expected = IllegalStateException::class)
+ fun onAnimationEnded_throwsWhenNoSuchAnimation() =
+ kosmos.runTest {
+ underTest.onAnimationStarted(KeyguardState.LOCKSCREEN, KeyguardState.AOD)
+ underTest.onAnimationStarted(KeyguardState.AOD, KeyguardState.ALTERNATE_BOUNCER)
+ assertThat(underTest.activeAnimations).hasSize(2)
+
+ underTest.onAnimationEnded(KeyguardState.AOD, KeyguardState.LOCKSCREEN)
+ }
+
+ @Test(expected = IllegalStateException::class)
+ fun onAnimationCanceled_throwsWhenNoSuchAnimation() =
+ kosmos.runTest {
+ underTest.onAnimationStarted(KeyguardState.LOCKSCREEN, KeyguardState.AOD)
+ underTest.onAnimationStarted(KeyguardState.AOD, KeyguardState.ALTERNATE_BOUNCER)
+ assertThat(underTest.activeAnimations).hasSize(2)
+
+ underTest.onAnimationCanceled(KeyguardState.AOD, KeyguardState.LOCKSCREEN)
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelTest.kt
index bd0fb68a9c42..af025273458f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelTest.kt
@@ -21,7 +21,6 @@ import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.biometrics.authController
import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository
-import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.flags.DisableSceneContainer
import com.android.systemui.flags.andSceneContainer
import com.android.systemui.keyguard.data.repository.fakeKeyguardClockRepository
@@ -29,7 +28,12 @@ import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepos
import com.android.systemui.keyguard.data.repository.keyguardOcclusionRepository
import com.android.systemui.keyguard.shared.model.ClockSize
import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.transition.fakeKeyguardTransitionAnimationCallback
+import com.android.systemui.keyguard.shared.transition.keyguardTransitionAnimationCallbackDelegator
import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.collectLastValue
+import com.android.systemui.kosmos.runCurrent
+import com.android.systemui.kosmos.runTest
import com.android.systemui.kosmos.testScope
import com.android.systemui.lifecycle.activateIn
import com.android.systemui.res.R
@@ -42,8 +46,7 @@ import com.android.systemui.unfold.fakeUnfoldTransitionProgressProvider
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import java.util.Locale
-import kotlinx.coroutines.test.runCurrent
-import kotlinx.coroutines.test.runTest
+import kotlinx.coroutines.Job
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -56,7 +59,8 @@ class LockscreenContentViewModelTest(flags: FlagsParameterization) : SysuiTestCa
private val kosmos: Kosmos = testKosmos()
- lateinit var underTest: LockscreenContentViewModel
+ private lateinit var underTest: LockscreenContentViewModel
+ private val activationJob = Job()
companion object {
@JvmStatic
@@ -74,225 +78,207 @@ class LockscreenContentViewModelTest(flags: FlagsParameterization) : SysuiTestCa
fun setup() {
with(kosmos) {
shadeRepository.setShadeLayoutWide(false)
- underTest = lockscreenContentViewModel
- underTest.activateIn(testScope)
+ underTest =
+ lockscreenContentViewModelFactory.create(fakeKeyguardTransitionAnimationCallback)
+ underTest.activateIn(testScope, activationJob)
}
}
@Test
fun isUdfpsVisible_withUdfps_true() =
- with(kosmos) {
- testScope.runTest {
- whenever(authController.isUdfpsSupported).thenReturn(true)
- assertThat(underTest.isUdfpsVisible).isTrue()
- }
+ kosmos.runTest {
+ whenever(authController.isUdfpsSupported).thenReturn(true)
+ assertThat(underTest.isUdfpsVisible).isTrue()
}
@Test
fun isUdfpsVisible_withoutUdfps_false() =
- with(kosmos) {
- testScope.runTest {
- whenever(authController.isUdfpsSupported).thenReturn(false)
- assertThat(underTest.isUdfpsVisible).isFalse()
- }
+ kosmos.runTest {
+ whenever(authController.isUdfpsSupported).thenReturn(false)
+ assertThat(underTest.isUdfpsVisible).isFalse()
}
@Test
@DisableSceneContainer
fun clockSize_withLargeClock_true() =
- with(kosmos) {
- testScope.runTest {
- val clockSize by collectLastValue(underTest.clockSize)
- fakeKeyguardClockRepository.setClockSize(ClockSize.LARGE)
- assertThat(clockSize).isEqualTo(ClockSize.LARGE)
- }
+ kosmos.runTest {
+ val clockSize by collectLastValue(underTest.clockSize)
+ fakeKeyguardClockRepository.setClockSize(ClockSize.LARGE)
+ assertThat(clockSize).isEqualTo(ClockSize.LARGE)
}
@Test
@DisableSceneContainer
fun clockSize_withSmallClock_false() =
- with(kosmos) {
- testScope.runTest {
- val clockSize by collectLastValue(underTest.clockSize)
- fakeKeyguardClockRepository.setClockSize(ClockSize.SMALL)
- assertThat(clockSize).isEqualTo(ClockSize.SMALL)
- }
+ kosmos.runTest {
+ val clockSize by collectLastValue(underTest.clockSize)
+ fakeKeyguardClockRepository.setClockSize(ClockSize.SMALL)
+ assertThat(clockSize).isEqualTo(ClockSize.SMALL)
}
@Test
fun areNotificationsVisible_splitShadeTrue_true() =
- with(kosmos) {
- testScope.runTest {
- val areNotificationsVisible by collectLastValue(underTest.areNotificationsVisible())
- shadeRepository.setShadeLayoutWide(true)
- fakeKeyguardClockRepository.setClockSize(ClockSize.LARGE)
+ kosmos.runTest {
+ val areNotificationsVisible by collectLastValue(underTest.areNotificationsVisible())
+ shadeRepository.setShadeLayoutWide(true)
+ fakeKeyguardClockRepository.setClockSize(ClockSize.LARGE)
- assertThat(areNotificationsVisible).isTrue()
- }
+ assertThat(areNotificationsVisible).isTrue()
}
@Test
fun areNotificationsVisible_dualShadeWideOnLockscreen_true() =
- with(kosmos) {
- testScope.runTest {
- val areNotificationsVisible by collectLastValue(underTest.areNotificationsVisible())
- kosmos.enableDualShade()
- shadeRepository.setShadeLayoutWide(true)
- fakeKeyguardClockRepository.setClockSize(ClockSize.LARGE)
+ kosmos.runTest {
+ val areNotificationsVisible by collectLastValue(underTest.areNotificationsVisible())
+ kosmos.enableDualShade()
+ shadeRepository.setShadeLayoutWide(true)
+ fakeKeyguardClockRepository.setClockSize(ClockSize.LARGE)
- assertThat(areNotificationsVisible).isTrue()
- }
+ assertThat(areNotificationsVisible).isTrue()
}
@Test
@DisableSceneContainer
fun areNotificationsVisible_withSmallClock_true() =
- with(kosmos) {
- testScope.runTest {
- val areNotificationsVisible by collectLastValue(underTest.areNotificationsVisible())
- fakeKeyguardClockRepository.setClockSize(ClockSize.SMALL)
- assertThat(areNotificationsVisible).isTrue()
- }
+ kosmos.runTest {
+ val areNotificationsVisible by collectLastValue(underTest.areNotificationsVisible())
+ fakeKeyguardClockRepository.setClockSize(ClockSize.SMALL)
+ assertThat(areNotificationsVisible).isTrue()
}
@Test
@DisableSceneContainer
fun areNotificationsVisible_withLargeClock_false() =
- with(kosmos) {
- testScope.runTest {
- val areNotificationsVisible by collectLastValue(underTest.areNotificationsVisible())
- fakeKeyguardClockRepository.setClockSize(ClockSize.LARGE)
- assertThat(areNotificationsVisible).isFalse()
- }
+ kosmos.runTest {
+ val areNotificationsVisible by collectLastValue(underTest.areNotificationsVisible())
+ fakeKeyguardClockRepository.setClockSize(ClockSize.LARGE)
+ assertThat(areNotificationsVisible).isFalse()
}
@Test
fun isShadeLayoutWide_withConfigTrue_true() =
- with(kosmos) {
- testScope.runTest {
- val isShadeLayoutWide by collectLastValue(underTest.isShadeLayoutWide)
- shadeRepository.setShadeLayoutWide(true)
+ kosmos.runTest {
+ val isShadeLayoutWide by collectLastValue(underTest.isShadeLayoutWide)
+ shadeRepository.setShadeLayoutWide(true)
- assertThat(isShadeLayoutWide).isTrue()
- }
+ assertThat(isShadeLayoutWide).isTrue()
}
@Test
fun isShadeLayoutWide_withConfigFalse_false() =
- with(kosmos) {
- testScope.runTest {
- val isShadeLayoutWide by collectLastValue(underTest.isShadeLayoutWide)
- shadeRepository.setShadeLayoutWide(false)
+ kosmos.runTest {
+ val isShadeLayoutWide by collectLastValue(underTest.isShadeLayoutWide)
+ shadeRepository.setShadeLayoutWide(false)
- assertThat(isShadeLayoutWide).isFalse()
- }
+ assertThat(isShadeLayoutWide).isFalse()
}
@Test
fun unfoldTranslations() =
- with(kosmos) {
- testScope.runTest {
- val maxTranslation = prepareConfiguration()
- val translations by collectLastValue(underTest.unfoldTranslations)
-
- val unfoldProvider = fakeUnfoldTransitionProgressProvider
- unfoldProvider.onTransitionStarted()
- assertThat(translations?.start).isEqualTo(0f)
- assertThat(translations?.end).isEqualTo(-0f)
-
- repeat(10) { repetition ->
- val transitionProgress = 0.1f * (repetition + 1)
- unfoldProvider.onTransitionProgress(transitionProgress)
- assertThat(translations?.start)
- .isEqualTo((1 - transitionProgress) * maxTranslation)
- assertThat(translations?.end)
- .isEqualTo(-(1 - transitionProgress) * maxTranslation)
- }
-
- unfoldProvider.onTransitionFinishing()
- assertThat(translations?.start).isEqualTo(0f)
- assertThat(translations?.end).isEqualTo(-0f)
-
- unfoldProvider.onTransitionFinished()
- assertThat(translations?.start).isEqualTo(0f)
- assertThat(translations?.end).isEqualTo(-0f)
+ kosmos.runTest {
+ val maxTranslation = prepareConfiguration()
+ val translations by collectLastValue(underTest.unfoldTranslations)
+
+ val unfoldProvider = fakeUnfoldTransitionProgressProvider
+ unfoldProvider.onTransitionStarted()
+ assertThat(translations?.start).isEqualTo(0f)
+ assertThat(translations?.end).isEqualTo(-0f)
+
+ repeat(10) { repetition ->
+ val transitionProgress = 0.1f * (repetition + 1)
+ unfoldProvider.onTransitionProgress(transitionProgress)
+ assertThat(translations?.start).isEqualTo((1 - transitionProgress) * maxTranslation)
+ assertThat(translations?.end).isEqualTo(-(1 - transitionProgress) * maxTranslation)
}
+
+ unfoldProvider.onTransitionFinishing()
+ assertThat(translations?.start).isEqualTo(0f)
+ assertThat(translations?.end).isEqualTo(-0f)
+
+ unfoldProvider.onTransitionFinished()
+ assertThat(translations?.start).isEqualTo(0f)
+ assertThat(translations?.end).isEqualTo(-0f)
}
@Test
fun isContentVisible_whenNotOccluded_visible() =
- with(kosmos) {
- testScope.runTest {
- val isContentVisible by collectLastValue(underTest.isContentVisible)
+ kosmos.runTest {
+ val isContentVisible by collectLastValue(underTest.isContentVisible)
- keyguardOcclusionRepository.setShowWhenLockedActivityInfo(false, null)
- runCurrent()
- assertThat(isContentVisible).isTrue()
- }
+ keyguardOcclusionRepository.setShowWhenLockedActivityInfo(false, null)
+ runCurrent()
+ assertThat(isContentVisible).isTrue()
}
@Test
fun isContentVisible_whenOccluded_notVisible() =
- with(kosmos) {
- testScope.runTest {
- val isContentVisible by collectLastValue(underTest.isContentVisible)
-
- keyguardOcclusionRepository.setShowWhenLockedActivityInfo(true, null)
- fakeKeyguardTransitionRepository.transitionTo(
- KeyguardState.LOCKSCREEN,
- KeyguardState.OCCLUDED,
- )
- runCurrent()
- assertThat(isContentVisible).isFalse()
- }
+ kosmos.runTest {
+ val isContentVisible by collectLastValue(underTest.isContentVisible)
+
+ keyguardOcclusionRepository.setShowWhenLockedActivityInfo(true, null)
+ fakeKeyguardTransitionRepository.transitionTo(
+ KeyguardState.LOCKSCREEN,
+ KeyguardState.OCCLUDED,
+ )
+ runCurrent()
+ assertThat(isContentVisible).isFalse()
}
@Test
fun isContentVisible_whenOccluded_notVisible_evenIfShadeShown() =
- with(kosmos) {
- testScope.runTest {
- val isContentVisible by collectLastValue(underTest.isContentVisible)
-
- keyguardOcclusionRepository.setShowWhenLockedActivityInfo(true, null)
- fakeKeyguardTransitionRepository.transitionTo(
- KeyguardState.LOCKSCREEN,
- KeyguardState.OCCLUDED,
- )
- runCurrent()
-
- sceneInteractor.snapToScene(Scenes.Shade, "")
- runCurrent()
- assertThat(isContentVisible).isFalse()
- }
+ kosmos.runTest {
+ val isContentVisible by collectLastValue(underTest.isContentVisible)
+
+ keyguardOcclusionRepository.setShowWhenLockedActivityInfo(true, null)
+ fakeKeyguardTransitionRepository.transitionTo(
+ KeyguardState.LOCKSCREEN,
+ KeyguardState.OCCLUDED,
+ )
+ runCurrent()
+
+ sceneInteractor.snapToScene(Scenes.Shade, "")
+ runCurrent()
+ assertThat(isContentVisible).isFalse()
+ }
+
+ @Test
+ fun activate_setsDelegate_onKeyguardTransitionAnimationCallbackDelegator() =
+ kosmos.runTest {
+ runCurrent()
+ assertThat(keyguardTransitionAnimationCallbackDelegator.delegate)
+ .isSameInstanceAs(fakeKeyguardTransitionAnimationCallback)
+ }
+
+ @Test
+ fun deactivate_clearsDelegate_onKeyguardTransitionAnimationCallbackDelegator() =
+ kosmos.runTest {
+ activationJob.cancel()
+ runCurrent()
+ assertThat(keyguardTransitionAnimationCallbackDelegator.delegate).isNull()
}
@Test
fun isContentVisible_whenOccluded_notVisibleInOccluded_visibleInAod() =
- with(kosmos) {
- testScope.runTest {
- val isContentVisible by collectLastValue(underTest.isContentVisible)
- keyguardOcclusionRepository.setShowWhenLockedActivityInfo(true, null)
- fakeKeyguardTransitionRepository.transitionTo(
- KeyguardState.LOCKSCREEN,
- KeyguardState.OCCLUDED,
- )
- runCurrent()
-
- sceneInteractor.snapToScene(Scenes.Shade, "")
- runCurrent()
- assertThat(isContentVisible).isFalse()
-
- fakeKeyguardTransitionRepository.transitionTo(
- KeyguardState.OCCLUDED,
- KeyguardState.AOD,
- )
- runCurrent()
-
- sceneInteractor.snapToScene(Scenes.Lockscreen, "")
- runCurrent()
-
- assertThat(isContentVisible).isTrue()
- }
+ kosmos.runTest {
+ val isContentVisible by collectLastValue(underTest.isContentVisible)
+ keyguardOcclusionRepository.setShowWhenLockedActivityInfo(true, null)
+ fakeKeyguardTransitionRepository.transitionTo(
+ KeyguardState.LOCKSCREEN,
+ KeyguardState.OCCLUDED,
+ )
+ runCurrent()
+
+ sceneInteractor.snapToScene(Scenes.Shade, "")
+ runCurrent()
+ assertThat(isContentVisible).isFalse()
+
+ fakeKeyguardTransitionRepository.transitionTo(KeyguardState.OCCLUDED, KeyguardState.AOD)
+ runCurrent()
+
+ sceneInteractor.snapToScene(Scenes.Lockscreen, "")
+ runCurrent()
+
+ assertThat(isContentVisible).isTrue()
}
private fun prepareConfiguration(): Int {
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/dagger/BouncerLoggerModule.kt b/packages/SystemUI/src/com/android/systemui/bouncer/dagger/BouncerLoggerModule.kt
index 8b253077a3dd..ab2ff2f84e65 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/dagger/BouncerLoggerModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/dagger/BouncerLoggerModule.kt
@@ -17,6 +17,7 @@
package com.android.systemui.bouncer.dagger
import com.android.systemui.CoreStartable
+import com.android.systemui.bouncer.domain.interactor.BouncerMessageAuditLogger
import com.android.systemui.bouncer.log.BouncerLoggerStartable
import dagger.Binds
import dagger.Module
@@ -30,4 +31,9 @@ interface BouncerLoggerModule {
@IntoMap
@ClassKey(BouncerLoggerStartable::class)
fun bindBouncerLoggerStartable(impl: BouncerLoggerStartable): CoreStartable
+
+ @Binds
+ @IntoMap
+ @ClassKey(BouncerMessageAuditLogger::class)
+ fun bind(impl: BouncerMessageAuditLogger): CoreStartable
}
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageAuditLogger.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageAuditLogger.kt
index 355ce6a3798f..64d33b2648c2 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageAuditLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageAuditLogger.kt
@@ -17,13 +17,13 @@
package com.android.systemui.bouncer.domain.interactor
import android.util.Log
+import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.systemui.CoreStartable
import com.android.systemui.bouncer.data.repository.BouncerMessageRepository
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
-import com.android.app.tracing.coroutines.launchTraced as launch
private val TAG = BouncerMessageAuditLogger::class.simpleName!!
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
index 905bbe226bb7..908413db22dd 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
@@ -61,6 +61,7 @@ import com.android.systemui.keyguard.data.repository.KeyguardRepositoryModule;
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionBootInteractor;
import com.android.systemui.keyguard.domain.interactor.StartKeyguardTransitionModule;
+import com.android.systemui.keyguard.ui.binder.SideFpsProgressBarViewBinder;
import com.android.systemui.keyguard.ui.transitions.BlurConfig;
import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransitionModule;
import com.android.systemui.keyguard.ui.view.AlternateBouncerWindowViewBinder;
@@ -271,4 +272,10 @@ public interface KeyguardModule {
@IntoMap
@ClassKey(AlternateBouncerWindowViewBinder.class)
CoreStartable bindsAlternateBouncerWindowViewBinder(AlternateBouncerWindowViewBinder binder);
+
+ /** A silly lint rule made me write this, this is a self-documenting function! */
+ @Binds
+ @IntoMap
+ @ClassKey(SideFpsProgressBarViewBinder.class)
+ CoreStartable bindSideFpsProgressBarViewBinder(SideFpsProgressBarViewBinder impl);
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt
index 3956901aaa88..126b375efb7d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt
@@ -16,33 +16,30 @@
package com.android.systemui.keyguard.data.repository
-import com.android.systemui.CoreStartable
import com.android.systemui.bouncer.data.repository.BouncerMessageRepository
import com.android.systemui.bouncer.data.repository.BouncerMessageRepositoryImpl
import com.android.systemui.bouncer.data.repository.KeyguardBouncerRepository
import com.android.systemui.bouncer.data.repository.KeyguardBouncerRepositoryImpl
-import com.android.systemui.bouncer.domain.interactor.BouncerMessageAuditLogger
-import com.android.systemui.keyguard.ui.binder.SideFpsProgressBarViewBinder
+import com.android.systemui.keyguard.shared.transition.KeyguardTransitionAnimationCallback
+import com.android.systemui.keyguard.shared.transition.KeyguardTransitionAnimationCallbackDelegator
import dagger.Binds
import dagger.Module
-import dagger.multibindings.ClassKey
-import dagger.multibindings.IntoMap
@Module
interface KeyguardRepositoryModule {
@Binds fun keyguardRepository(impl: KeyguardRepositoryImpl): KeyguardRepository
@Binds
- @IntoMap
- @ClassKey(SideFpsProgressBarViewBinder::class)
- fun bindSideFpsProgressBarViewBinder(viewBinder: SideFpsProgressBarViewBinder): CoreStartable
-
- @Binds
fun keyguardSurfaceBehindRepository(
impl: KeyguardSurfaceBehindRepositoryImpl
): KeyguardSurfaceBehindRepository
@Binds
+ fun keyguardTransitionAnimationCallback(
+ impl: KeyguardTransitionAnimationCallbackDelegator
+ ): KeyguardTransitionAnimationCallback
+
+ @Binds
fun keyguardTransitionRepository(
impl: KeyguardTransitionRepositoryImpl
): KeyguardTransitionRepository
@@ -68,11 +65,6 @@ interface KeyguardRepositoryModule {
@Binds
fun bouncerMessageRepository(impl: BouncerMessageRepositoryImpl): BouncerMessageRepository
- @Binds
- @IntoMap
- @ClassKey(BouncerMessageAuditLogger::class)
- fun bind(impl: BouncerMessageAuditLogger): CoreStartable
-
@Binds fun trustRepository(impl: TrustRepositoryImpl): TrustRepository
@Binds fun keyguardClockRepository(impl: KeyguardClockRepositoryImpl): KeyguardClockRepository
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt
index 24f2493c626d..2a1cb12c153e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt
@@ -33,6 +33,7 @@ import com.android.systemui.keyguard.shared.model.TransitionInfo
import com.android.systemui.keyguard.shared.model.TransitionModeOnCanceled
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.android.systemui.keyguard.shared.transition.KeyguardTransitionAnimationCallback
import java.util.UUID
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
@@ -131,7 +132,10 @@ interface KeyguardTransitionRepository {
@SysUISingleton
class KeyguardTransitionRepositoryImpl
@Inject
-constructor(@Main val mainDispatcher: CoroutineDispatcher) : KeyguardTransitionRepository {
+constructor(
+ @Main private val mainDispatcher: CoroutineDispatcher,
+ private val transitionCallback: KeyguardTransitionAnimationCallback,
+) : KeyguardTransitionRepository {
/**
* Each transition between [KeyguardState]s will have an associated Flow. In order to collect
* these events, clients should call [transition].
@@ -252,16 +256,19 @@ constructor(@Main val mainDispatcher: CoroutineDispatcher) : KeyguardTransitionR
animatorListener =
object : AnimatorListenerAdapter() {
override fun onAnimationStart(animation: Animator) {
+ transitionCallback.onAnimationStarted(info.from, info.to)
emitTransition(
TransitionStep(info, startingValue, TransitionState.STARTED)
)
}
override fun onAnimationCancel(animation: Animator) {
+ transitionCallback.onAnimationCanceled(info.from, info.to)
endAnimation(lastStep.value, TransitionState.CANCELED)
}
override fun onAnimationEnd(animation: Animator) {
+ transitionCallback.onAnimationEnded(info.from, info.to)
endAnimation(1f, TransitionState.FINISHED)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/transition/KeyguardTransitionAnimationCallback.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/transition/KeyguardTransitionAnimationCallback.kt
new file mode 100644
index 000000000000..6ecc5d3abea3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/transition/KeyguardTransitionAnimationCallback.kt
@@ -0,0 +1,34 @@
+/*
+ * 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.keyguard.shared.transition
+
+import com.android.systemui.keyguard.shared.model.KeyguardState
+
+/**
+ * Defines an interface for classes that can be notified when a keyguard transition starts, ends, or
+ * is canceled.
+ */
+interface KeyguardTransitionAnimationCallback {
+
+ /** Notifies that an animation from [from] to [to] has started. */
+ fun onAnimationStarted(from: KeyguardState, to: KeyguardState)
+
+ /** Notifies that an animation from [from] to [to] has ended. */
+ fun onAnimationEnded(from: KeyguardState, to: KeyguardState)
+
+ /** Notifies that an animation from [from] to [to] has been canceled. */
+ fun onAnimationCanceled(from: KeyguardState, to: KeyguardState)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/transition/KeyguardTransitionAnimationCallbackDelegator.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/transition/KeyguardTransitionAnimationCallbackDelegator.kt
new file mode 100644
index 000000000000..5de9e326c0c2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/transition/KeyguardTransitionAnimationCallbackDelegator.kt
@@ -0,0 +1,39 @@
+/*
+ * 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.keyguard.shared.transition
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import javax.inject.Inject
+
+@SysUISingleton
+class KeyguardTransitionAnimationCallbackDelegator @Inject constructor() :
+ KeyguardTransitionAnimationCallback {
+
+ var delegate: KeyguardTransitionAnimationCallback? = null
+
+ override fun onAnimationStarted(from: KeyguardState, to: KeyguardState) {
+ delegate?.onAnimationStarted(from, to)
+ }
+
+ override fun onAnimationEnded(from: KeyguardState, to: KeyguardState) {
+ delegate?.onAnimationEnded(from, to)
+ }
+
+ override fun onAnimationCanceled(from: KeyguardState, to: KeyguardState) {
+ delegate?.onAnimationCanceled(from, to)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModel.kt
index b6a3b6aaba14..3e3a89a55f66 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModel.kt
@@ -27,10 +27,13 @@ import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.ClockSize
import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.transition.KeyguardTransitionAnimationCallback
+import com.android.systemui.keyguard.shared.transition.KeyguardTransitionAnimationCallbackDelegator
import com.android.systemui.lifecycle.ExclusiveActivatable
import com.android.systemui.res.R
import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.unfold.domain.interactor.UnfoldTransitionInteractor
+import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import kotlinx.coroutines.CoroutineScope
@@ -57,6 +60,9 @@ constructor(
private val unfoldTransitionInteractor: UnfoldTransitionInteractor,
private val deviceEntryInteractor: DeviceEntryInteractor,
private val transitionInteractor: KeyguardTransitionInteractor,
+ private val keyguardTransitionAnimationCallbackDelegator:
+ KeyguardTransitionAnimationCallbackDelegator,
+ @Assisted private val keyguardTransitionAnimationCallback: KeyguardTransitionAnimationCallback,
) : ExclusiveActivatable() {
@VisibleForTesting val clockSize = clockInteractor.clockSize
@@ -79,29 +85,36 @@ constructor(
override suspend fun onActivated(): Nothing {
coroutineScope {
- launch {
- combine(
- unfoldTransitionInteractor.unfoldTranslationX(isOnStartSide = true),
- unfoldTransitionInteractor.unfoldTranslationX(isOnStartSide = false),
- ) { start, end ->
- UnfoldTranslations(start = start, end = end)
- }
- .collect { _unfoldTranslations.value = it }
+ try {
+ keyguardTransitionAnimationCallbackDelegator.delegate =
+ keyguardTransitionAnimationCallback
+ launch {
+ combine(
+ unfoldTransitionInteractor.unfoldTranslationX(isOnStartSide = true),
+ unfoldTransitionInteractor.unfoldTranslationX(isOnStartSide = false),
+ ) { start, end ->
+ UnfoldTranslations(start = start, end = end)
+ }
+ .collect { _unfoldTranslations.value = it }
+ }
+
+ launch {
+ transitionInteractor
+ .transitionValue(KeyguardState.OCCLUDED)
+ .map { it > 0f }
+ .collect { fullyOrPartiallyOccluded ->
+ // Content is visible unless we're OCCLUDED. Currently, we don't have
+ // nice
+ // animations into and out of OCCLUDED, so the lockscreen/AOD content is
+ // hidden immediately upon entering/exiting OCCLUDED.
+ _isContentVisible.value = !fullyOrPartiallyOccluded
+ }
+ }
+
+ awaitCancellation()
+ } finally {
+ keyguardTransitionAnimationCallbackDelegator.delegate = null
}
-
- launch {
- transitionInteractor
- .transitionValue(KeyguardState.OCCLUDED)
- .map { it > 0f }
- .collect { fullyOrPartiallyOccluded ->
- // Content is visible unless we're OCCLUDED. Currently, we don't have nice
- // animations into and out of OCCLUDED, so the lockscreen/AOD content is
- // hidden immediately upon entering/exiting OCCLUDED.
- _isContentVisible.value = !fullyOrPartiallyOccluded
- }
- }
-
- awaitCancellation()
}
}
@@ -151,6 +164,8 @@ constructor(
@AssistedFactory
interface Factory {
- fun create(): LockscreenContentViewModel
+ fun create(
+ keyguardTransitionAnimationCallback: KeyguardTransitionAnimationCallback
+ ): LockscreenContentViewModel
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt
index 77c40a1e8eef..f14a9f5a1885 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt
@@ -37,6 +37,7 @@ import com.android.systemui.keyguard.shared.model.TransitionInfo
import com.android.systemui.keyguard.shared.model.TransitionModeOnCanceled
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.android.systemui.keyguard.shared.transition.keyguardTransitionAnimationCallback
import com.android.systemui.keyguard.util.FrameCallbackProvider
import com.android.systemui.keyguard.util.KeyguardTransitionRunner
import com.android.systemui.kosmos.testDispatcher
@@ -81,7 +82,11 @@ class KeyguardTransitionRepositoryTest : SysuiTestCase() {
@Before
fun setUp() {
- underTest = KeyguardTransitionRepositoryImpl(Dispatchers.Main)
+ underTest =
+ KeyguardTransitionRepositoryImpl(
+ Dispatchers.Main,
+ kosmos.keyguardTransitionAnimationCallback,
+ )
runBlocking {
callbackProvider = FrameCallbackProvider(testScope.backgroundScope)
withContext(Dispatchers.Main) {
@@ -411,7 +416,11 @@ class KeyguardTransitionRepositoryTest : SysuiTestCase() {
@Test
fun simulateRaceConditionIsProcessedInOrder() =
testScope.runTest {
- val ktr = KeyguardTransitionRepositoryImpl(kosmos.testDispatcher)
+ val ktr =
+ KeyguardTransitionRepositoryImpl(
+ kosmos.testDispatcher,
+ kosmos.keyguardTransitionAnimationCallback,
+ )
val steps by collectValues(ktr.transitions.dropWhile { step -> step.from == OFF })
// Add a delay to the first transition in order to attempt to have the second transition
@@ -448,7 +457,11 @@ class KeyguardTransitionRepositoryTest : SysuiTestCase() {
@Test
fun simulateRaceConditionIsProcessedInOrderUsingUpdateTransition() =
testScope.runTest {
- val ktr = KeyguardTransitionRepositoryImpl(kosmos.testDispatcher)
+ val ktr =
+ KeyguardTransitionRepositoryImpl(
+ kosmos.testDispatcher,
+ kosmos.keyguardTransitionAnimationCallback,
+ )
val steps by collectValues(ktr.transitions.dropWhile { step -> step.from == OFF })
// Begin a manual transition
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryKosmos.kt
index e9eea83d54df..dacc78a35092 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryKosmos.kt
@@ -16,6 +16,7 @@
package com.android.systemui.keyguard.data.repository
+import com.android.systemui.keyguard.shared.transition.keyguardTransitionAnimationCallbackDelegator
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.kosmos.testScope
@@ -28,4 +29,9 @@ var Kosmos.fakeKeyguardTransitionRepository by
var Kosmos.fakeKeyguardTransitionRepositorySpy: FakeKeyguardTransitionRepository by
Kosmos.Fixture { spy(fakeKeyguardTransitionRepository) }
var Kosmos.realKeyguardTransitionRepository: KeyguardTransitionRepository by
- Kosmos.Fixture { KeyguardTransitionRepositoryImpl(testDispatcher) }
+ Kosmos.Fixture {
+ KeyguardTransitionRepositoryImpl(
+ testDispatcher,
+ keyguardTransitionAnimationCallbackDelegator,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/shared/transition/FakeKeyguardTransitionAnimationCallback.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/shared/transition/FakeKeyguardTransitionAnimationCallback.kt
new file mode 100644
index 000000000000..6a1ced52c7c9
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/shared/transition/FakeKeyguardTransitionAnimationCallback.kt
@@ -0,0 +1,55 @@
+/*
+ * 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.keyguard.shared.transition
+
+import com.android.systemui.keyguard.shared.model.KeyguardState
+
+class FakeKeyguardTransitionAnimationCallback : KeyguardTransitionAnimationCallback {
+
+ private val _activeAnimations = mutableListOf<TransitionAnimation>()
+ /** The animations that have been started but not yet ended nor canceled. */
+ val activeAnimations: List<TransitionAnimation>
+ get() = _activeAnimations.toList()
+
+ private val _finishedAnimations = mutableListOf<TransitionAnimation>()
+ /** The animations that have ended. */
+ val finishedAnimations: List<TransitionAnimation>
+ get() = _finishedAnimations.toList()
+
+ private val _canceledAnimations = mutableListOf<TransitionAnimation>()
+ /** The animations that have been canceled. */
+ val canceledAnimations: List<TransitionAnimation>
+ get() = _canceledAnimations.toList()
+
+ override fun onAnimationStarted(from: KeyguardState, to: KeyguardState) {
+ _activeAnimations.add(TransitionAnimation(from = from, to = to))
+ }
+
+ override fun onAnimationEnded(from: KeyguardState, to: KeyguardState) {
+ val animation = TransitionAnimation(from = from, to = to)
+ check(_activeAnimations.remove(animation)) { "Ending an animation that wasn't started!" }
+ _finishedAnimations.add(animation)
+ }
+
+ override fun onAnimationCanceled(from: KeyguardState, to: KeyguardState) {
+ val animation = TransitionAnimation(from = from, to = to)
+ check(_activeAnimations.remove(animation)) { "Canceling an animation that wasn't started!" }
+ _canceledAnimations.add(animation)
+ }
+
+ data class TransitionAnimation(val from: KeyguardState, val to: KeyguardState)
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/shared/transition/KeyguardTransitionAnimationCallbackKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/shared/transition/KeyguardTransitionAnimationCallbackKosmos.kt
new file mode 100644
index 000000000000..a445d2c17cd3
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/shared/transition/KeyguardTransitionAnimationCallbackKosmos.kt
@@ -0,0 +1,32 @@
+/*
+ * 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.keyguard.shared.transition
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+
+val Kosmos.keyguardTransitionAnimationCallbackDelegator by Fixture {
+ KeyguardTransitionAnimationCallbackDelegator()
+}
+
+val Kosmos.keyguardTransitionAnimationCallback: KeyguardTransitionAnimationCallback by Fixture {
+ fakeKeyguardTransitionAnimationCallback
+}
+
+val Kosmos.fakeKeyguardTransitionAnimationCallback by Fixture {
+ FakeKeyguardTransitionAnimationCallback()
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelKosmos.kt
index 78d44d4917fe..dd13b8b143ae 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelKosmos.kt
@@ -21,20 +21,31 @@ import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
import com.android.systemui.keyguard.domain.interactor.keyguardBlueprintInteractor
import com.android.systemui.keyguard.domain.interactor.keyguardClockInteractor
import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.transition.KeyguardTransitionAnimationCallback
+import com.android.systemui.keyguard.shared.transition.keyguardTransitionAnimationCallbackDelegator
import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
import com.android.systemui.shade.domain.interactor.shadeInteractor
import com.android.systemui.unfold.domain.interactor.unfoldTransitionInteractor
-val Kosmos.lockscreenContentViewModel by
- Kosmos.Fixture {
- LockscreenContentViewModel(
- clockInteractor = keyguardClockInteractor,
- interactor = keyguardBlueprintInteractor,
- authController = authController,
- touchHandling = keyguardTouchHandlingViewModel,
- shadeInteractor = shadeInteractor,
- unfoldTransitionInteractor = unfoldTransitionInteractor,
- deviceEntryInteractor = deviceEntryInteractor,
- transitionInteractor = keyguardTransitionInteractor,
- )
+val Kosmos.lockscreenContentViewModelFactory by Fixture {
+ object : LockscreenContentViewModel.Factory {
+ override fun create(
+ keyguardTransitionAnimationCallback: KeyguardTransitionAnimationCallback
+ ): LockscreenContentViewModel {
+ return LockscreenContentViewModel(
+ clockInteractor = keyguardClockInteractor,
+ interactor = keyguardBlueprintInteractor,
+ authController = authController,
+ touchHandling = keyguardTouchHandlingViewModel,
+ shadeInteractor = shadeInteractor,
+ unfoldTransitionInteractor = unfoldTransitionInteractor,
+ deviceEntryInteractor = deviceEntryInteractor,
+ transitionInteractor = keyguardTransitionInteractor,
+ keyguardTransitionAnimationCallbackDelegator =
+ keyguardTransitionAnimationCallbackDelegator,
+ keyguardTransitionAnimationCallback = keyguardTransitionAnimationCallback,
+ )
+ }
}
+}