Loading core/java/com/android/internal/jank/Cuj.java +22 −1 Original line number Diff line number Diff line Loading @@ -516,8 +516,21 @@ public class Cuj { */ public static final int CUJ_AMBIENT_CUE_COLLAPSE = 148; /** * Tracking transition from primary auth (PIN/pattern/password bouncer) to the biometric auth * bouncer during secure lock device two-factor authentication. */ public static final int CUJ_BOUNCER_SECURE_LOCK_DEVICE_BIOMETRIC_AUTH_APPEAR = 149; /** * Tracking bouncer dismissal following two-factor authentication completion in secure * lock device. */ public static final int CUJ_BOUNCER_SECURE_LOCK_DEVICE_BIOMETRIC_AUTH_DISAPPEAR = 150; // When adding a CUJ, update this and make sure to also update CUJ_TO_STATSD_INTERACTION_TYPE. @VisibleForTesting static final int LAST_CUJ = CUJ_AMBIENT_CUE_COLLAPSE; @VisibleForTesting static final int LAST_CUJ = CUJ_BOUNCER_SECURE_LOCK_DEVICE_BIOMETRIC_AUTH_DISAPPEAR; /** @hide */ @IntDef({ Loading Loading @@ -658,6 +671,8 @@ public class Cuj { CUJ_AMBIENT_CUE_HIDE, CUJ_AMBIENT_CUE_EXPAND, CUJ_AMBIENT_CUE_COLLAPSE, CUJ_BOUNCER_SECURE_LOCK_DEVICE_BIOMETRIC_AUTH_APPEAR, CUJ_BOUNCER_SECURE_LOCK_DEVICE_BIOMETRIC_AUTH_DISAPPEAR }) @Retention(RetentionPolicy.SOURCE) public @interface CujType {} Loading Loading @@ -808,6 +823,8 @@ public class Cuj { CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_AMBIENT_CUE_HIDE] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__AMBIENT_CUE_HIDE; CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_AMBIENT_CUE_EXPAND] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__AMBIENT_CUE_EXPAND; CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_AMBIENT_CUE_COLLAPSE] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__AMBIENT_CUE_COLLAPSE; CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_BOUNCER_SECURE_LOCK_DEVICE_BIOMETRIC_AUTH_APPEAR] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__BOUNCER_SECURE_LOCK_DEVICE_BIOMETRIC_AUTH_APPEAR; CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_BOUNCER_SECURE_LOCK_DEVICE_BIOMETRIC_AUTH_DISAPPEAR] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__BOUNCER_SECURE_LOCK_DEVICE_BIOMETRIC_AUTH_DISAPPEAR; } private Cuj() { Loading Loading @@ -1100,6 +1117,10 @@ public class Cuj { return "AMBIENT_CUE_EXPAND"; case CUJ_AMBIENT_CUE_COLLAPSE: return "AMBIENT_CUE_COLLAPSE"; case CUJ_BOUNCER_SECURE_LOCK_DEVICE_BIOMETRIC_AUTH_APPEAR: return "BOUNCER_SECURE_LOCK_DEVICE_BIOMETRIC_AUTH_APPEAR"; case CUJ_BOUNCER_SECURE_LOCK_DEVICE_BIOMETRIC_AUTH_DISAPPEAR: return "BOUNCER_SECURE_LOCK_DEVICE_BIOMETRIC_AUTH_DISAPPEAR"; } return "UNKNOWN"; } Loading packages/SystemUI/multivalentTests/src/com/android/systemui/securelockdevice/ui/composable/SecureLockDeviceBiometricAuthContentTest.kt 0 → 100644 +125 −0 Original line number Diff line number Diff line /* * 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.securelockdevice.ui.composable import android.platform.test.annotations.EnableFlags import android.security.Flags import android.view.View import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.internal.jank.Cuj import com.android.internal.jank.InteractionJankMonitor import com.android.systemui.SysuiTestCase import org.junit.Test import org.junit.runner.RunWith import org.mockito.kotlin.mock import org.mockito.kotlin.never import org.mockito.kotlin.verify @SmallTest @RunWith(AndroidJUnit4::class) @EnableFlags(Flags.FLAG_SECURE_LOCK_DEVICE) class SecureLockDeviceBiometricAuthContentTest : SysuiTestCase() { private val interactionJankMonitor: InteractionJankMonitor = mock() private val view: View = mock() private val onDisappearAnimationFinished: () -> Unit = mock() @Test fun handleJankMonitoring_tracksAppear() { // Simulate start of transition when target state becomes visible handleJankMonitoring( currentState = false, isCurrentStateIdle = false, targetState = true, isReadyToDismissBiometricAuth = false, interactionJankMonitor = interactionJankMonitor, view = view, onDisappearAnimationFinished = onDisappearAnimationFinished, ) // Verify jank monitoring begins for appear verify(interactionJankMonitor) .begin(view, Cuj.CUJ_BOUNCER_SECURE_LOCK_DEVICE_BIOMETRIC_AUTH_APPEAR) // Simulate end of transition when state becomes idle at visible handleJankMonitoring( currentState = true, isCurrentStateIdle = true, targetState = true, isReadyToDismissBiometricAuth = false, interactionJankMonitor = interactionJankMonitor, view = view, onDisappearAnimationFinished = onDisappearAnimationFinished, ) // THEN jank monitoring ends for appear verify(interactionJankMonitor).end(Cuj.CUJ_BOUNCER_SECURE_LOCK_DEVICE_BIOMETRIC_AUTH_APPEAR) } @Test fun handleJankMonitoring_tracksDisappear() { // Simulate start of transition when target state becomes not visible handleJankMonitoring( currentState = true, isCurrentStateIdle = false, targetState = false, isReadyToDismissBiometricAuth = true, interactionJankMonitor = interactionJankMonitor, view = view, onDisappearAnimationFinished = onDisappearAnimationFinished, ) // Verify jank monitoring begins for disappear verify(interactionJankMonitor) .begin(view, Cuj.CUJ_BOUNCER_SECURE_LOCK_DEVICE_BIOMETRIC_AUTH_DISAPPEAR) // Simulate end of transition when not visible state becomes idle handleJankMonitoring( currentState = false, isCurrentStateIdle = true, targetState = false, isReadyToDismissBiometricAuth = true, interactionJankMonitor = interactionJankMonitor, view = view, onDisappearAnimationFinished = onDisappearAnimationFinished, ) // Verify jank monitoring ends for disappear and the callback is invoked verify(interactionJankMonitor) .end(Cuj.CUJ_BOUNCER_SECURE_LOCK_DEVICE_BIOMETRIC_AUTH_DISAPPEAR) verify(onDisappearAnimationFinished).invoke() } @Test fun handleJankMonitoring_doesNotTrackDisappear_whenNotReadyToDismiss() { // Simulate start of transition when target state becomes not visible, but not ready to // dismiss (animations haven't finished playing) handleJankMonitoring( currentState = true, isCurrentStateIdle = false, targetState = false, isReadyToDismissBiometricAuth = false, interactionJankMonitor = interactionJankMonitor, view = view, onDisappearAnimationFinished = onDisappearAnimationFinished, ) // Verify jank monitoring does NOT begin verify(interactionJankMonitor, never()) .begin(view, Cuj.CUJ_BOUNCER_SECURE_LOCK_DEVICE_BIOMETRIC_AUTH_DISAPPEAR) } } packages/SystemUI/src/com/android/systemui/securelockdevice/ui/composable/SecureLockDeviceBiometricAuthContent.kt +60 −1 Original line number Diff line number Diff line Loading @@ -15,6 +15,8 @@ */ package com.android.systemui.securelockdevice.ui.composable import android.view.View import androidx.annotation.VisibleForTesting import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.core.Animatable import androidx.compose.animation.core.MutableTransitionState Loading Loading @@ -43,6 +45,7 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalView import androidx.compose.ui.res.colorResource import androidx.compose.ui.res.dimensionResource import androidx.compose.ui.res.stringResource Loading @@ -61,6 +64,8 @@ import com.airbnb.lottie.compose.rememberLottieComposition import com.android.compose.animation.Easings import com.android.compose.modifiers.height import com.android.compose.modifiers.width import com.android.internal.jank.Cuj import com.android.internal.jank.InteractionJankMonitor import com.android.systemui.Flags.bpColors import com.android.systemui.biometrics.BiometricAuthIconAssets import com.android.systemui.bouncer.shared.model.SecureLockDeviceBouncerActionButtonModel Loading @@ -87,7 +92,13 @@ fun SecureLockDeviceContent( secureLockDeviceViewModelFactory.create() } val view = LocalView.current val interactionJankMonitor: InteractionJankMonitor = secureLockDeviceViewModel.interactionJankMonitor val isVisible = secureLockDeviceViewModel.isVisible val isReadyToDismissBiometricAuth = secureLockDeviceViewModel.isReadyToDismissBiometricAuth val visibleState = remember { MutableTransitionState(isVisible) } /** This effect is run when the composable enters the composition */ Loading @@ -110,7 +121,17 @@ fun SecureLockDeviceContent( * the UI have finished playing on the UI. */ LaunchedEffect(visibleState.currentState, visibleState.targetState, visibleState.isIdle) { // TODO(b/436359935) report interaction jank metrics handleJankMonitoring( currentState = visibleState.currentState, isCurrentStateIdle = visibleState.isIdle, targetState = visibleState.targetState, isReadyToDismissBiometricAuth = isReadyToDismissBiometricAuth, interactionJankMonitor = interactionJankMonitor, view = view, onDisappearAnimationFinished = { secureLockDeviceViewModel.onDisappearAnimationFinished() }, ) } /** Animates the biometric auth content in and out of view. */ Loading Loading @@ -154,6 +175,44 @@ fun SecureLockDeviceContent( } } /** Handles InteractionJankMonitor tracking for the appear and disappear animations. */ @VisibleForTesting fun handleJankMonitoring( currentState: Boolean, isCurrentStateIdle: Boolean, targetState: Boolean, isReadyToDismissBiometricAuth: Boolean, interactionJankMonitor: InteractionJankMonitor, view: View, onDisappearAnimationFinished: () -> Unit, ) { if (!currentState && targetState) { // Start appear animation // Start appear animation interactionJankMonitor.begin( /* v = */ view, /* cujType = */ Cuj.CUJ_BOUNCER_SECURE_LOCK_DEVICE_BIOMETRIC_AUTH_APPEAR, ) } else if (currentState && isCurrentStateIdle) { // Appear animation complete interactionJankMonitor.end( /* cujType = */ Cuj.CUJ_BOUNCER_SECURE_LOCK_DEVICE_BIOMETRIC_AUTH_APPEAR ) } else if (currentState && !targetState) { // Disappear animation started if (isReadyToDismissBiometricAuth) { interactionJankMonitor.begin( /* v = */ view, /* cujType = */ Cuj.CUJ_BOUNCER_SECURE_LOCK_DEVICE_BIOMETRIC_AUTH_DISAPPEAR, ) } } else if (!currentState && isCurrentStateIdle) { // Disappear animation complete if (isReadyToDismissBiometricAuth) { interactionJankMonitor.end( /* cujType = */ Cuj.CUJ_BOUNCER_SECURE_LOCK_DEVICE_BIOMETRIC_AUTH_DISAPPEAR ) onDisappearAnimationFinished() } } } @Composable private fun BiometricIconLottie( viewModel: SecureLockDeviceBiometricAuthContentViewModel, Loading packages/SystemUI/src/com/android/systemui/securelockdevice/ui/viewmodel/SecureLockDeviceBiometricAuthContentViewModel.kt +2 −0 Original line number Diff line number Diff line Loading @@ -22,6 +22,7 @@ import android.util.Log import android.view.accessibility.AccessibilityManager import androidx.annotation.VisibleForTesting import androidx.compose.runtime.getValue import com.android.internal.jank.InteractionJankMonitor import com.android.systemui.biometrics.shared.model.BiometricModality import com.android.systemui.biometrics.ui.viewmodel.BiometricAuthIconViewModel import com.android.systemui.biometrics.ui.viewmodel.PromptAuthState Loading Loading @@ -74,6 +75,7 @@ constructor( deviceEntryFingerprintAuthInteractor: DeviceEntryFingerprintAuthInteractor, private val secureLockDeviceInteractor: SecureLockDeviceInteractor, val udfpsAccessibilityOverlayViewModel: AlternateBouncerUdfpsAccessibilityOverlayViewModel, val interactionJankMonitor: InteractionJankMonitor, ) : HydratedActivatable() { /** @see SecureLockDeviceInteractor.isSecureLockDeviceEnabled */ val isSecureLockDeviceEnabled = secureLockDeviceInteractor.isSecureLockDeviceEnabled Loading packages/SystemUI/tests/utils/src/com/android/systemui/securelockdevice/ui/viewmodel/SecureLockDeviceBiometricAuthContentViewModelKosmos.kt +2 −0 Original line number Diff line number Diff line Loading @@ -24,6 +24,7 @@ import com.android.systemui.deviceentry.domain.interactor.biometricMessageIntera import com.android.systemui.deviceentry.domain.interactor.deviceEntryFaceAuthInteractor import com.android.systemui.deviceentry.domain.interactor.deviceEntryFingerprintAuthInteractor import com.android.systemui.haptics.msdl.bouncerHapticPlayer import com.android.systemui.jank.interactionJankMonitor import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.Kosmos.Fixture import com.android.systemui.kosmos.applicationCoroutineScope Loading @@ -41,6 +42,7 @@ var Kosmos.secureLockDeviceBiometricAuthContentViewModel by Fixture { deviceEntryFingerprintAuthInteractor = deviceEntryFingerprintAuthInteractor, secureLockDeviceInteractor = secureLockDeviceInteractor, udfpsAccessibilityOverlayViewModel = alternateBouncerUdfpsAccessibilityOverlayViewModel, interactionJankMonitor = interactionJankMonitor, ) } Loading Loading
core/java/com/android/internal/jank/Cuj.java +22 −1 Original line number Diff line number Diff line Loading @@ -516,8 +516,21 @@ public class Cuj { */ public static final int CUJ_AMBIENT_CUE_COLLAPSE = 148; /** * Tracking transition from primary auth (PIN/pattern/password bouncer) to the biometric auth * bouncer during secure lock device two-factor authentication. */ public static final int CUJ_BOUNCER_SECURE_LOCK_DEVICE_BIOMETRIC_AUTH_APPEAR = 149; /** * Tracking bouncer dismissal following two-factor authentication completion in secure * lock device. */ public static final int CUJ_BOUNCER_SECURE_LOCK_DEVICE_BIOMETRIC_AUTH_DISAPPEAR = 150; // When adding a CUJ, update this and make sure to also update CUJ_TO_STATSD_INTERACTION_TYPE. @VisibleForTesting static final int LAST_CUJ = CUJ_AMBIENT_CUE_COLLAPSE; @VisibleForTesting static final int LAST_CUJ = CUJ_BOUNCER_SECURE_LOCK_DEVICE_BIOMETRIC_AUTH_DISAPPEAR; /** @hide */ @IntDef({ Loading Loading @@ -658,6 +671,8 @@ public class Cuj { CUJ_AMBIENT_CUE_HIDE, CUJ_AMBIENT_CUE_EXPAND, CUJ_AMBIENT_CUE_COLLAPSE, CUJ_BOUNCER_SECURE_LOCK_DEVICE_BIOMETRIC_AUTH_APPEAR, CUJ_BOUNCER_SECURE_LOCK_DEVICE_BIOMETRIC_AUTH_DISAPPEAR }) @Retention(RetentionPolicy.SOURCE) public @interface CujType {} Loading Loading @@ -808,6 +823,8 @@ public class Cuj { CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_AMBIENT_CUE_HIDE] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__AMBIENT_CUE_HIDE; CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_AMBIENT_CUE_EXPAND] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__AMBIENT_CUE_EXPAND; CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_AMBIENT_CUE_COLLAPSE] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__AMBIENT_CUE_COLLAPSE; CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_BOUNCER_SECURE_LOCK_DEVICE_BIOMETRIC_AUTH_APPEAR] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__BOUNCER_SECURE_LOCK_DEVICE_BIOMETRIC_AUTH_APPEAR; CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_BOUNCER_SECURE_LOCK_DEVICE_BIOMETRIC_AUTH_DISAPPEAR] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__BOUNCER_SECURE_LOCK_DEVICE_BIOMETRIC_AUTH_DISAPPEAR; } private Cuj() { Loading Loading @@ -1100,6 +1117,10 @@ public class Cuj { return "AMBIENT_CUE_EXPAND"; case CUJ_AMBIENT_CUE_COLLAPSE: return "AMBIENT_CUE_COLLAPSE"; case CUJ_BOUNCER_SECURE_LOCK_DEVICE_BIOMETRIC_AUTH_APPEAR: return "BOUNCER_SECURE_LOCK_DEVICE_BIOMETRIC_AUTH_APPEAR"; case CUJ_BOUNCER_SECURE_LOCK_DEVICE_BIOMETRIC_AUTH_DISAPPEAR: return "BOUNCER_SECURE_LOCK_DEVICE_BIOMETRIC_AUTH_DISAPPEAR"; } return "UNKNOWN"; } Loading
packages/SystemUI/multivalentTests/src/com/android/systemui/securelockdevice/ui/composable/SecureLockDeviceBiometricAuthContentTest.kt 0 → 100644 +125 −0 Original line number Diff line number Diff line /* * 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.securelockdevice.ui.composable import android.platform.test.annotations.EnableFlags import android.security.Flags import android.view.View import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.internal.jank.Cuj import com.android.internal.jank.InteractionJankMonitor import com.android.systemui.SysuiTestCase import org.junit.Test import org.junit.runner.RunWith import org.mockito.kotlin.mock import org.mockito.kotlin.never import org.mockito.kotlin.verify @SmallTest @RunWith(AndroidJUnit4::class) @EnableFlags(Flags.FLAG_SECURE_LOCK_DEVICE) class SecureLockDeviceBiometricAuthContentTest : SysuiTestCase() { private val interactionJankMonitor: InteractionJankMonitor = mock() private val view: View = mock() private val onDisappearAnimationFinished: () -> Unit = mock() @Test fun handleJankMonitoring_tracksAppear() { // Simulate start of transition when target state becomes visible handleJankMonitoring( currentState = false, isCurrentStateIdle = false, targetState = true, isReadyToDismissBiometricAuth = false, interactionJankMonitor = interactionJankMonitor, view = view, onDisappearAnimationFinished = onDisappearAnimationFinished, ) // Verify jank monitoring begins for appear verify(interactionJankMonitor) .begin(view, Cuj.CUJ_BOUNCER_SECURE_LOCK_DEVICE_BIOMETRIC_AUTH_APPEAR) // Simulate end of transition when state becomes idle at visible handleJankMonitoring( currentState = true, isCurrentStateIdle = true, targetState = true, isReadyToDismissBiometricAuth = false, interactionJankMonitor = interactionJankMonitor, view = view, onDisappearAnimationFinished = onDisappearAnimationFinished, ) // THEN jank monitoring ends for appear verify(interactionJankMonitor).end(Cuj.CUJ_BOUNCER_SECURE_LOCK_DEVICE_BIOMETRIC_AUTH_APPEAR) } @Test fun handleJankMonitoring_tracksDisappear() { // Simulate start of transition when target state becomes not visible handleJankMonitoring( currentState = true, isCurrentStateIdle = false, targetState = false, isReadyToDismissBiometricAuth = true, interactionJankMonitor = interactionJankMonitor, view = view, onDisappearAnimationFinished = onDisappearAnimationFinished, ) // Verify jank monitoring begins for disappear verify(interactionJankMonitor) .begin(view, Cuj.CUJ_BOUNCER_SECURE_LOCK_DEVICE_BIOMETRIC_AUTH_DISAPPEAR) // Simulate end of transition when not visible state becomes idle handleJankMonitoring( currentState = false, isCurrentStateIdle = true, targetState = false, isReadyToDismissBiometricAuth = true, interactionJankMonitor = interactionJankMonitor, view = view, onDisappearAnimationFinished = onDisappearAnimationFinished, ) // Verify jank monitoring ends for disappear and the callback is invoked verify(interactionJankMonitor) .end(Cuj.CUJ_BOUNCER_SECURE_LOCK_DEVICE_BIOMETRIC_AUTH_DISAPPEAR) verify(onDisappearAnimationFinished).invoke() } @Test fun handleJankMonitoring_doesNotTrackDisappear_whenNotReadyToDismiss() { // Simulate start of transition when target state becomes not visible, but not ready to // dismiss (animations haven't finished playing) handleJankMonitoring( currentState = true, isCurrentStateIdle = false, targetState = false, isReadyToDismissBiometricAuth = false, interactionJankMonitor = interactionJankMonitor, view = view, onDisappearAnimationFinished = onDisappearAnimationFinished, ) // Verify jank monitoring does NOT begin verify(interactionJankMonitor, never()) .begin(view, Cuj.CUJ_BOUNCER_SECURE_LOCK_DEVICE_BIOMETRIC_AUTH_DISAPPEAR) } }
packages/SystemUI/src/com/android/systemui/securelockdevice/ui/composable/SecureLockDeviceBiometricAuthContent.kt +60 −1 Original line number Diff line number Diff line Loading @@ -15,6 +15,8 @@ */ package com.android.systemui.securelockdevice.ui.composable import android.view.View import androidx.annotation.VisibleForTesting import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.core.Animatable import androidx.compose.animation.core.MutableTransitionState Loading Loading @@ -43,6 +45,7 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalView import androidx.compose.ui.res.colorResource import androidx.compose.ui.res.dimensionResource import androidx.compose.ui.res.stringResource Loading @@ -61,6 +64,8 @@ import com.airbnb.lottie.compose.rememberLottieComposition import com.android.compose.animation.Easings import com.android.compose.modifiers.height import com.android.compose.modifiers.width import com.android.internal.jank.Cuj import com.android.internal.jank.InteractionJankMonitor import com.android.systemui.Flags.bpColors import com.android.systemui.biometrics.BiometricAuthIconAssets import com.android.systemui.bouncer.shared.model.SecureLockDeviceBouncerActionButtonModel Loading @@ -87,7 +92,13 @@ fun SecureLockDeviceContent( secureLockDeviceViewModelFactory.create() } val view = LocalView.current val interactionJankMonitor: InteractionJankMonitor = secureLockDeviceViewModel.interactionJankMonitor val isVisible = secureLockDeviceViewModel.isVisible val isReadyToDismissBiometricAuth = secureLockDeviceViewModel.isReadyToDismissBiometricAuth val visibleState = remember { MutableTransitionState(isVisible) } /** This effect is run when the composable enters the composition */ Loading @@ -110,7 +121,17 @@ fun SecureLockDeviceContent( * the UI have finished playing on the UI. */ LaunchedEffect(visibleState.currentState, visibleState.targetState, visibleState.isIdle) { // TODO(b/436359935) report interaction jank metrics handleJankMonitoring( currentState = visibleState.currentState, isCurrentStateIdle = visibleState.isIdle, targetState = visibleState.targetState, isReadyToDismissBiometricAuth = isReadyToDismissBiometricAuth, interactionJankMonitor = interactionJankMonitor, view = view, onDisappearAnimationFinished = { secureLockDeviceViewModel.onDisappearAnimationFinished() }, ) } /** Animates the biometric auth content in and out of view. */ Loading Loading @@ -154,6 +175,44 @@ fun SecureLockDeviceContent( } } /** Handles InteractionJankMonitor tracking for the appear and disappear animations. */ @VisibleForTesting fun handleJankMonitoring( currentState: Boolean, isCurrentStateIdle: Boolean, targetState: Boolean, isReadyToDismissBiometricAuth: Boolean, interactionJankMonitor: InteractionJankMonitor, view: View, onDisappearAnimationFinished: () -> Unit, ) { if (!currentState && targetState) { // Start appear animation // Start appear animation interactionJankMonitor.begin( /* v = */ view, /* cujType = */ Cuj.CUJ_BOUNCER_SECURE_LOCK_DEVICE_BIOMETRIC_AUTH_APPEAR, ) } else if (currentState && isCurrentStateIdle) { // Appear animation complete interactionJankMonitor.end( /* cujType = */ Cuj.CUJ_BOUNCER_SECURE_LOCK_DEVICE_BIOMETRIC_AUTH_APPEAR ) } else if (currentState && !targetState) { // Disappear animation started if (isReadyToDismissBiometricAuth) { interactionJankMonitor.begin( /* v = */ view, /* cujType = */ Cuj.CUJ_BOUNCER_SECURE_LOCK_DEVICE_BIOMETRIC_AUTH_DISAPPEAR, ) } } else if (!currentState && isCurrentStateIdle) { // Disappear animation complete if (isReadyToDismissBiometricAuth) { interactionJankMonitor.end( /* cujType = */ Cuj.CUJ_BOUNCER_SECURE_LOCK_DEVICE_BIOMETRIC_AUTH_DISAPPEAR ) onDisappearAnimationFinished() } } } @Composable private fun BiometricIconLottie( viewModel: SecureLockDeviceBiometricAuthContentViewModel, Loading
packages/SystemUI/src/com/android/systemui/securelockdevice/ui/viewmodel/SecureLockDeviceBiometricAuthContentViewModel.kt +2 −0 Original line number Diff line number Diff line Loading @@ -22,6 +22,7 @@ import android.util.Log import android.view.accessibility.AccessibilityManager import androidx.annotation.VisibleForTesting import androidx.compose.runtime.getValue import com.android.internal.jank.InteractionJankMonitor import com.android.systemui.biometrics.shared.model.BiometricModality import com.android.systemui.biometrics.ui.viewmodel.BiometricAuthIconViewModel import com.android.systemui.biometrics.ui.viewmodel.PromptAuthState Loading Loading @@ -74,6 +75,7 @@ constructor( deviceEntryFingerprintAuthInteractor: DeviceEntryFingerprintAuthInteractor, private val secureLockDeviceInteractor: SecureLockDeviceInteractor, val udfpsAccessibilityOverlayViewModel: AlternateBouncerUdfpsAccessibilityOverlayViewModel, val interactionJankMonitor: InteractionJankMonitor, ) : HydratedActivatable() { /** @see SecureLockDeviceInteractor.isSecureLockDeviceEnabled */ val isSecureLockDeviceEnabled = secureLockDeviceInteractor.isSecureLockDeviceEnabled Loading
packages/SystemUI/tests/utils/src/com/android/systemui/securelockdevice/ui/viewmodel/SecureLockDeviceBiometricAuthContentViewModelKosmos.kt +2 −0 Original line number Diff line number Diff line Loading @@ -24,6 +24,7 @@ import com.android.systemui.deviceentry.domain.interactor.biometricMessageIntera import com.android.systemui.deviceentry.domain.interactor.deviceEntryFaceAuthInteractor import com.android.systemui.deviceentry.domain.interactor.deviceEntryFingerprintAuthInteractor import com.android.systemui.haptics.msdl.bouncerHapticPlayer import com.android.systemui.jank.interactionJankMonitor import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.Kosmos.Fixture import com.android.systemui.kosmos.applicationCoroutineScope Loading @@ -41,6 +42,7 @@ var Kosmos.secureLockDeviceBiometricAuthContentViewModel by Fixture { deviceEntryFingerprintAuthInteractor = deviceEntryFingerprintAuthInteractor, secureLockDeviceInteractor = secureLockDeviceInteractor, udfpsAccessibilityOverlayViewModel = alternateBouncerUdfpsAccessibilityOverlayViewModel, interactionJankMonitor = interactionJankMonitor, ) } Loading