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

Commit 605f3ba7 authored by Beverly's avatar Beverly
Browse files

Update lockscreenAlpha depending on DozeTransitionFlows

<KeyguardState>ToDozingTransitionViewModels now rely on
DozingTransitionFlows for lockscreenAlpha. Now, lockscreenAlpha
updates based on the DozeTransitionModel state (ie:
DOZE vs PULSING).

This CL also:
 - Moves the device wakeup to _after_ biometric
 unlock events are sent to consumers. This is to avoid
 waking up before a biometric wakeup gets to control
 the DOZING/AOD => LOCKSCREEN/BOUNCER/GONE animation.

 - Always wakes and unlocks from pulsing instead of
 WAKE_ONLY for non-bypass face auth. Currently face auth
 will only run if the user interacts with the fp sensor
 while pulsing, so we can always wake and unlock. This will
 trigger a DOZING => GONE transition instead of DOZING => LOCKSCREEN.
 To test this, intentionally tap UDFPS too quickly from screen off;
 succeed non-bypass face auth; observe transition to GONE instead
 of LOCKSCREEN.

Test: atest DozingTransitionFlowsTest
Test: enroll UDFPS; enable Screen-off Fingerprint Unlock;
succeed UDFPS from screen off and failed UDFPS from screen off;
observe transitions & expected UI
Flag: com.android.systemui.new_dozing_keyguard_states
Bug: 415704030

Change-Id: I27f3ec82cc127c7597c22bb4b1f1a73d06a643bd
parent 3a2623ec
Loading
Loading
Loading
Loading
+178 −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.keyguard.ui.viewmodel

import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.flags.DisableSceneContainer
import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.DozeStateModel
import com.android.systemui.keyguard.shared.model.DozeTransitionModel
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionState.RUNNING
import com.android.systemui.keyguard.shared.model.TransitionState.STARTED
import com.android.systemui.keyguard.shared.model.TransitionStep
import com.android.systemui.kosmos.testScope
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Test
import org.junit.runner.RunWith

@DisableSceneContainer
@SmallTest
@RunWith(AndroidJUnit4::class)
class DozingTransitionFlowsTest : SysuiTestCase() {
    private val kosmos = testKosmos()
    private val testScope = kosmos.testScope
    private val underTest by lazy { kosmos.dozingTransitionFlows }

    @Test
    fun lockscreenAlpha_dozing_dozePulsing() =
        testScope.runTest {
            val lockscreenAlpha by collectLastValue(underTest.lockscreenAlpha)
            // Last startedKeyguardTransitionStep is to DOZING
            kosmos.fakeKeyguardTransitionRepository.sendTransitionStep(
                lockscreenToDozingStep(0f, STARTED)
            )
            runCurrent()
            kosmos.fakeKeyguardRepository.setDozeTransitionModel(
                DozeTransitionModel(to = DozeStateModel.DOZE_PULSING)
            )
            runCurrent()
            assertThat(lockscreenAlpha).isEqualTo(1f)
        }

    @Test
    fun lockscreenAlpha_dozing_dozePulsingAuthUI() =
        testScope.runTest {
            val lockscreenAlpha by collectLastValue(underTest.lockscreenAlpha)
            // Last startedKeyguardTransitionStep is to DOZING
            kosmos.fakeKeyguardTransitionRepository.sendTransitionStep(
                lockscreenToDozingStep(0f, STARTED)
            )
            runCurrent()
            kosmos.fakeKeyguardRepository.setDozeTransitionModel(
                DozeTransitionModel(to = DozeStateModel.DOZE_PULSING_AUTH_UI)
            )
            runCurrent()
            assertThat(lockscreenAlpha).isEqualTo(1f)
        }

    @Test
    fun lockscreenAlpha_keyguardStateDozing_dozeTransitionModelDoze() =
        testScope.runTest {
            val lockscreenAlpha by collectLastValue(underTest.lockscreenAlpha)
            // Last startedKeyguardTransitionStep is to DOZING
            kosmos.fakeKeyguardTransitionRepository.sendTransitionStep(
                lockscreenToDozingStep(0f, STARTED)
            )
            runCurrent()
            kosmos.fakeKeyguardRepository.setDozeTransitionModel(
                DozeTransitionModel(to = DozeStateModel.DOZE)
            )
            runCurrent()
            assertThat(lockscreenAlpha).isEqualTo(0f)
        }

    @Test
    fun lockscreenAlpha_keyguardStateDozing_dozeTransitionModelDozePulsingWithoutUI() =
        testScope.runTest {
            val lockscreenAlpha by collectLastValue(underTest.lockscreenAlpha)
            // Last startedKeyguardTransitionStep is to DOZING
            kosmos.fakeKeyguardTransitionRepository.sendTransitionStep(
                lockscreenToDozingStep(0f, STARTED)
            )
            runCurrent()
            kosmos.fakeKeyguardRepository.setDozeTransitionModel(
                DozeTransitionModel(to = DozeStateModel.DOZE_PULSING_WITHOUT_UI)
            )
            runCurrent()
            assertThat(lockscreenAlpha).isEqualTo(0f)
        }

    @Test
    fun lockscreenAlpha_keyguardStateDozing_dozeTransitionModelUnhandledStates() =
        testScope.runTest {
            val lockscreenAlpha by collectLastValue(underTest.lockscreenAlpha)
            // Last startedKeyguardTransitionStep is to DOZING
            kosmos.fakeKeyguardTransitionRepository.sendTransitionStep(
                lockscreenToDozingStep(0f, STARTED)
            )
            runCurrent()
            val unhandledStates =
                listOf(
                    DozeStateModel.DOZE_AOD_PAUSED,
                    DozeStateModel.DOZE_AOD_PAUSING,
                    DozeStateModel.UNINITIALIZED,
                    DozeStateModel.INITIALIZED,
                    DozeStateModel.FINISH,
                    DozeStateModel.DOZE_PULSE_DONE,
                )
            for (unhandledState in unhandledStates) {
                kosmos.fakeKeyguardRepository.setDozeTransitionModel(
                    DozeTransitionModel(to = unhandledState)
                )
                runCurrent()
                assertThat(lockscreenAlpha).isNull()
            }
        }

    @Test
    fun lockscreenAlpha_notKeyguardStateDozing_nothingEmits() =
        testScope.runTest {
            val lockscreenAlpha by collectLastValue(underTest.lockscreenAlpha)
            // Last startedKeyguardTransitionStep is to AOD (not DOZING)
            kosmos.fakeKeyguardTransitionRepository.sendTransitionStep(
                lockscreenToAodStep(0f, STARTED)
            )
            runCurrent()
            kosmos.fakeKeyguardRepository.setDozeTransitionModel(
                DozeTransitionModel(to = DozeStateModel.DOZE_PULSING)
            )
            runCurrent()
            assertThat(lockscreenAlpha).isNull()
            kosmos.fakeKeyguardRepository.setDozeTransitionModel(
                DozeTransitionModel(to = DozeStateModel.DOZE_PULSING_BRIGHT)
            )
            runCurrent()
            assertThat(lockscreenAlpha).isNull()
        }

    private fun lockscreenToDozingStep(value: Float, transitionState: TransitionState = RUNNING) =
        TransitionStep(
            from = KeyguardState.LOCKSCREEN,
            to = KeyguardState.DOZING,
            value = value,
            transitionState = transitionState,
            ownerName = "dozingViewModelTest",
        )

    private fun lockscreenToAodStep(value: Float, transitionState: TransitionState = RUNNING) =
        TransitionStep(
            from = KeyguardState.LOCKSCREEN,
            to = KeyguardState.AOD,
            value = value,
            transitionState = transitionState,
            ownerName = "dozingViewModelTest",
        )
}
+1 −1
Original line number Diff line number Diff line
@@ -203,7 +203,7 @@ public class DozeServiceHostTest extends SysuiTestCase {
                        DozeLog.REASON_SENSOR_DOUBLE_TAP,
                        DozeLog.REASON_SENSOR_TAP,
                        DozeLog.REASON_SENSOR_UDFPS_LONG_PRESS,
                        DozeLog.REASON_USUDFPS_PULSE));
                        DozeLog.PULSE_REASON_FINGERPRINT_PULSE));

        doAnswer(invocation -> {
            DozeHost.PulseCallback callback = invocation.getArgument(0);
+17 −3
Original line number Diff line number Diff line
@@ -15,6 +15,7 @@
 */
package com.android.keyguard

import android.R
import android.app.NotificationManager.zenModeFromInterruptionFilter
import android.content.BroadcastReceiver
import android.content.Context
@@ -43,13 +44,13 @@ import com.android.systemui.dagger.qualifiers.DisplaySpecific
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.flags.FeatureFlagsClassic
import com.android.systemui.flags.Flags.REGION_SAMPLING
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.Edge
import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
import com.android.systemui.keyguard.shared.model.KeyguardState.DOZING
import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.ui.viewmodel.DozingToLockscreenTransitionViewModel
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.log.core.Logger
import com.android.systemui.plugins.clocks.AlarmData
@@ -75,6 +76,7 @@ import com.android.systemui.statusbar.policy.ZenModeController
import com.android.systemui.statusbar.policy.domain.interactor.ZenModeInteractor
import com.android.systemui.util.annotations.DeprecatedSysuiVisibleForTesting
import com.android.systemui.util.concurrency.DelayableExecutor
import dagger.Lazy
import java.util.Locale
import java.util.TimeZone
import java.util.concurrent.Executor
@@ -94,7 +96,6 @@ import kotlinx.coroutines.flow.merge
open class ClockEventController
@Inject
constructor(
    private val keyguardInteractor: KeyguardInteractor,
    private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
    private val broadcastDispatcher: BroadcastDispatcher,
    private val batteryController: BatteryController,
@@ -110,6 +111,7 @@ constructor(
    private val zenModeController: ZenModeController,
    private val zenModeInteractor: ZenModeInteractor,
    private val userTracker: UserTracker,
    private val dozingToLockscreenViewModel: Lazy<DozingToLockscreenTransitionViewModel>,
) {
    var loggers =
        listOf(
@@ -257,7 +259,7 @@ constructor(

    private fun isDarkTheme(): Boolean {
        val isLightTheme = TypedValue()
        context.theme.resolveAttribute(android.R.attr.isLightTheme, isLightTheme, true)
        context.theme.resolveAttribute(R.attr.isLightTheme, isLightTheme, true)
        return isLightTheme.data == 0
    }

@@ -478,6 +480,9 @@ constructor(
                    listenForAnyStateToAodTransition(this)
                    listenForAnyStateToLockscreenTransition(this)
                    listenForAnyStateToDozingTransition(this)
                    if (com.android.systemui.Flags.newDozingKeyguardStates()) {
                        listenForDozingToLockscreen(this)
                    }
                }
            }
        smallTimeListener?.update(shouldTimeListenerRun)
@@ -624,10 +629,19 @@ constructor(
                .transition(Edge.create(to = LOCKSCREEN))
                .filter { it.transitionState == TransitionState.STARTED }
                .filter { it.from != AOD }
                .filter {
                    !com.android.systemui.Flags.newDozingKeyguardStates() || it.from != DOZING
                }
                .collect { handleDoze(0f) }
        }
    }

    private fun listenForDozingToLockscreen(scope: CoroutineScope): Job {
        return scope.launch {
            dozingToLockscreenViewModel.get().clockDozeAmount.collect { handleDoze(it) }
        }
    }

    /**
     * When keyguard is displayed due to pulsing notifications when AOD is off, we should make sure
     * clock is in dozing state instead of LS state
+8 −2
Original line number Diff line number Diff line
@@ -73,8 +73,14 @@ constructor(
     * Whether the fingerprint sensor is present under the display as opposed to being on the power
     * button or behind/rear of the phone.
     */
    val isSensorUnderDisplay =
        fingerprintPropertyRepository.sensorType.map(FingerprintSensorType::isUdfps)
    val isSensorUnderDisplay: StateFlow<Boolean> =
        fingerprintPropertyRepository.sensorType
            .map(FingerprintSensorType::isUdfps)
            .stateIn(
                scope = applicationScope,
                started = SharingStarted.Eagerly,
                initialValue = fingerprintPropertyRepository.sensorType.value.isUdfps(),
            )

    /** True if it is ultrasonic udfps sensor, otherwise false. */
    val isUltrasonic: StateFlow<Boolean> =
+5 −0
Original line number Diff line number Diff line
@@ -147,6 +147,11 @@ public interface DozeHost {
         * Called when ultrasonic fingerprint auth events want the screen on to show info.
         */
        default void onUltrasonicUdfpsPulseWhileScreenOff(FingerprintAuthenticationStatus state) {}

        /**
         * Called when fingerprint auth events want the screen on to show info.
         */
        default void onFingerprintPulseWhileScreenOff(FingerprintAuthenticationStatus state) {}
    }

    interface PulseCallback {
Loading