Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 849b3881 authored by Treehugger Robot's avatar Treehugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Add system health CUJs" into main

parents 8f6dea7a 85c90f60
Loading
Loading
Loading
Loading
+22 −1
Original line number Diff line number Diff line
@@ -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({
@@ -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 {}
@@ -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() {
@@ -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";
    }
+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)
    }
}
+60 −1
Original line number Diff line number Diff line
@@ -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
@@ -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
@@ -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
@@ -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 */
@@ -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. */
@@ -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,
+2 −0
Original line number Diff line number Diff line
@@ -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
@@ -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
+2 −0
Original line number Diff line number Diff line
@@ -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
@@ -41,6 +42,7 @@ var Kosmos.secureLockDeviceBiometricAuthContentViewModel by Fixture {
        deviceEntryFingerprintAuthInteractor = deviceEntryFingerprintAuthInteractor,
        secureLockDeviceInteractor = secureLockDeviceInteractor,
        udfpsAccessibilityOverlayViewModel = alternateBouncerUdfpsAccessibilityOverlayViewModel,
        interactionJankMonitor = interactionJankMonitor,
    )
}