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

Commit 73a89177 authored by Chandru S's avatar Chandru S
Browse files

Use PowerManager.wakeup to wake up the device when it has not gone to sleep yet.

Bug: 319218123
Flag: ACONFIG com.android.systemui.rest_to_unlock TEAMFOOD
Test: manually, turn off the screen by pressing the power button and touch the power button FPS quickly
Change-Id: I679acc6c34664f47f0a7f6a2bc6049a0df4f45b2
parent abf9f57a
Loading
Loading
Loading
Loading
+4 −0
Original line number Diff line number Diff line
@@ -37,6 +37,8 @@ import com.android.systemui.biometrics.shared.model.DisplayRotation.ROTATION_90
import com.android.systemui.biometrics.shared.model.FingerprintSensorType
import com.android.systemui.biometrics.shared.model.SensorStrength
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.keyguard.data.repository.biometricSettingsRepository
import com.android.systemui.keyguard.data.repository.fakeBiometricSettingsRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.KeyguardState
@@ -112,6 +114,7 @@ class SideFpsSensorInteractorTest : SysuiTestCase() {
                windowManager,
                displayStateInteractor,
                Optional.of(fingerprintInteractiveToAuthProvider),
                kosmos.biometricSettingsRepository,
                kosmos.keyguardTransitionInteractor,
                SideFpsLogger(logcatLogBuffer("SfpsLogger"))
            )
@@ -420,6 +423,7 @@ class SideFpsSensorInteractorTest : SysuiTestCase() {
    @Test
    fun isProlongedTouchRequiredForAuthentication_dependsOnSettingsToggle() =
        testScope.runTest {
            kosmos.fakeBiometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(true)
            val isEnabled by collectLastValue(underTest.isProlongedTouchRequiredForAuthentication)
            setupFingerprint(FingerprintSensorType.POWER_BUTTON)

+189 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 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 android.content.applicationContext
import android.hardware.biometrics.BiometricFingerprintConstants
import android.os.PowerManager
import android.testing.TestableLooper
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.Flags
import com.android.systemui.SysuiTestCase
import com.android.systemui.biometrics.data.repository.fakeFingerprintPropertyRepository
import com.android.systemui.biometrics.domain.interactor.displayStateInteractor
import com.android.systemui.biometrics.domain.interactor.sideFpsSensorInteractor
import com.android.systemui.biometrics.fakeFingerprintInteractiveToAuthProvider
import com.android.systemui.biometrics.shared.model.FingerprintSensorType
import com.android.systemui.biometrics.shared.model.SensorStrength
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.deviceentry.domain.interactor.deviceEntryFingerprintAuthInteractor
import com.android.systemui.keyguard.data.repository.fakeBiometricSettingsRepository
import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFingerprintAuthRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
import com.android.systemui.keyguard.shared.model.AcquiredFingerprintAuthenticationStatus
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep
import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.kosmos.testScope
import com.android.systemui.power.data.repository.fakePowerRepository
import com.android.systemui.power.domain.interactor.powerInteractor
import com.android.systemui.res.R
import com.android.systemui.statusbar.phone.dozeServiceHost
import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.mock
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mockito.verify

@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@TestableLooper.RunWithLooper
@RunWith(AndroidJUnit4::class)
class SideFpsProgressBarViewModelTest : SysuiTestCase() {
    private val kosmos = testKosmos()
    private lateinit var underTest: SideFpsProgressBarViewModel
    private val testScope = kosmos.testScope
    private lateinit var mTestableLooper: TestableLooper

    @Before
    fun setup() {
        mTestableLooper = TestableLooper.get(this)
        allowTestableLooperAsMainThread()
    }

    private suspend fun setupRestToUnlockEnabled() {
        mSetFlagsRule.enableFlags(Flags.FLAG_REST_TO_UNLOCK)
        overrideResource(R.bool.config_restToUnlockSupported, true)
        kosmos.fakeFingerprintPropertyRepository.setProperties(
            1,
            SensorStrength.STRONG,
            FingerprintSensorType.POWER_BUTTON,
            mutableMapOf(Pair("sensor", mock()))
        )
        kosmos.fakeFingerprintInteractiveToAuthProvider.enabledForCurrentUser.value = true
        kosmos.fakeKeyguardTransitionRepository.sendTransitionStep(
            TransitionStep(
                from = KeyguardState.LOCKSCREEN,
                to = KeyguardState.AOD,
                value = 0.0f,
                transitionState = TransitionState.STARTED
            )
        )
        kosmos.fakeKeyguardTransitionRepository.sendTransitionStep(
            TransitionStep(
                from = KeyguardState.LOCKSCREEN,
                to = KeyguardState.AOD,
                value = 1.0f,
                transitionState = TransitionState.FINISHED
            )
        )
        kosmos.fakeBiometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(true)
    }

    @Test
    fun whenConfigDisabled_featureIsDisabled() =
        testScope.runTest {
            overrideResource(R.bool.config_restToUnlockSupported, false)
            underTest = createViewModel()
            val enabled by collectLastValue(underTest.isProlongedTouchRequiredForAuthentication)

            assertThat(enabled).isFalse()
        }

    @Test
    fun whenConfigEnabledSensorIsPowerButtonAndSettingsToggleIsEnabled_featureIsEnabled() =
        testScope.runTest {
            overrideResource(R.bool.config_restToUnlockSupported, true)
            underTest = createViewModel()
            val enabled by collectLastValue(underTest.isProlongedTouchRequiredForAuthentication)

            assertThat(enabled).isFalse()
            kosmos.fakeFingerprintPropertyRepository.setProperties(
                1,
                SensorStrength.STRONG,
                FingerprintSensorType.POWER_BUTTON,
                mutableMapOf(Pair("sensor", mock()))
            )
            assertThat(enabled).isFalse()

            kosmos.fakeFingerprintInteractiveToAuthProvider.enabledForCurrentUser.value = true
            kosmos.fakeBiometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(true)

            runCurrent()
            assertThat(enabled).isTrue()
        }

    @Test
    fun whenFingerprintAcquiredStartsWhenNotDozing_wakesUpDevice() =
        testScope.runTest {
            setupRestToUnlockEnabled()
            underTest = createViewModel()

            kosmos.fakeKeyguardRepository.setIsDozing(false)
            kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus(
                AcquiredFingerprintAuthenticationStatus(
                    BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_START
                )
            )

            runCurrent()

            assertThat(kosmos.fakePowerRepository.lastWakeReason)
                .isEqualTo(PowerManager.WAKE_REASON_BIOMETRIC)
        }

    @Test
    fun whenFingerprintAcquiredStartsWhenDozing_pulsesAod() =
        testScope.runTest {
            setupRestToUnlockEnabled()
            underTest = createViewModel()

            kosmos.fakeKeyguardRepository.setIsDozing(true)
            kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus(
                AcquiredFingerprintAuthenticationStatus(
                    BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_START
                )
            )

            runCurrent()

            verify(kosmos.dozeServiceHost).fireSideFpsAcquisitionStarted()
        }

    private fun createViewModel() =
        SideFpsProgressBarViewModel(
            kosmos.applicationContext,
            kosmos.deviceEntryFingerprintAuthInteractor,
            kosmos.sideFpsSensorInteractor,
            kosmos.dozeServiceHost,
            kosmos.keyguardInteractor,
            kosmos.displayStateInteractor,
            kosmos.testDispatcher,
            kosmos.applicationCoroutineScope,
            kosmos.powerInteractor,
        )
}
+18 −2
Original line number Diff line number Diff line
@@ -26,20 +26,24 @@ import com.android.systemui.biometrics.shared.model.DisplayRotation
import com.android.systemui.biometrics.shared.model.FingerprintSensorType
import com.android.systemui.biometrics.shared.model.isDefaultOrientation
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.log.SideFpsLogger
import com.android.systemui.res.R
import java.util.Optional
import javax.inject.Inject
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach

@ExperimentalCoroutinesApi
@SysUISingleton
class SideFpsSensorInteractor
@Inject
@@ -49,6 +53,7 @@ constructor(
    windowManager: WindowManager,
    displayStateInteractor: DisplayStateInteractor,
    fingerprintInteractiveToAuthProvider: Optional<FingerprintInteractiveToAuthProvider>,
    biometricSettingsRepository: BiometricSettingsRepository,
    keyguardTransitionInteractor: KeyguardTransitionInteractor,
    private val logger: SideFpsLogger,
) {
@@ -84,13 +89,24 @@ constructor(
            .map { it ?: 0L }
            .onEach { logger.authDurationChanged(it) }

    private val isSettingEnabled: Flow<Boolean> =
        biometricSettingsRepository.isFingerprintEnrolledAndEnabled
            .flatMapLatest { enabledAndEnrolled ->
                if (!enabledAndEnrolled || fingerprintInteractiveToAuthProvider.isEmpty) {
                    flowOf(false)
                } else {
                    fingerprintInteractiveToAuthProvider.get().enabledForCurrentUser
                }
            }
            .onEach { logger.restToUnlockSettingEnabledChanged(it) }

    val isProlongedTouchRequiredForAuthentication: Flow<Boolean> =
        if (fingerprintInteractiveToAuthProvider.isEmpty || !isProlongedTouchEnabledForDevice) {
        if (!isProlongedTouchEnabledForDevice) {
            flowOf(false)
        } else {
            combine(
                isAvailable,
                fingerprintInteractiveToAuthProvider.get().enabledForCurrentUser
                isSettingEnabled,
            ) { sfpsAvailable, isSettingEnabled ->
                sfpsAvailable && isSettingEnabled
            }
+1 −1
Original line number Diff line number Diff line
@@ -98,7 +98,7 @@ constructor(
    val dozeAmount: Flow<Float> = repository.linearDozeAmount

    /** Whether the system is in doze mode. */
    val isDozing: Flow<Boolean> = repository.isDozing
    val isDozing: StateFlow<Boolean> = repository.isDozing

    /** Receive an event for doze time tick */
    val dozeTimeTick: Flow<Long> = repository.dozeTimeTick
+53 −41
Original line number Diff line number Diff line
@@ -20,7 +20,7 @@ import android.animation.ValueAnimator
import android.content.Context
import android.graphics.Point
import androidx.annotation.VisibleForTesting
import androidx.core.animation.doOnEnd
import androidx.core.animation.addListener
import com.android.systemui.Flags
import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor
import com.android.systemui.biometrics.domain.interactor.SideFpsSensorInteractor
@@ -30,15 +30,18 @@ import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFingerprintAuthInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.keyguard.shared.model.AcquiredFingerprintAuthenticationStatus
import com.android.systemui.keyguard.shared.model.ErrorFingerprintAuthenticationStatus
import com.android.systemui.keyguard.shared.model.FailFingerprintAuthenticationStatus
import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus
import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.res.R
import com.android.systemui.statusbar.phone.DozeServiceHost
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
@@ -46,13 +49,14 @@ import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onCompletion
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch

@ExperimentalCoroutinesApi
@SysUISingleton
class SideFpsProgressBarViewModel
@Inject
@@ -63,9 +67,11 @@ constructor(
    // todo (b/317432075) Injecting DozeServiceHost directly instead of using it through
    //  DozeInteractor as DozeServiceHost already depends on DozeInteractor.
    private val dozeServiceHost: DozeServiceHost,
    private val keyguardInteractor: KeyguardInteractor,
    displayStateInteractor: DisplayStateInteractor,
    @Main private val mainDispatcher: CoroutineDispatcher,
    @Application private val applicationScope: CoroutineScope,
    private val powerInteractor: PowerInteractor,
) {
    private val _progress = MutableStateFlow(0.0f)
    private val _visible = MutableStateFlow(false)
@@ -176,31 +182,36 @@ constructor(
                    return@collectLatest
                }
                animatorJob =
                    combine(
                            sfpsSensorInteractor.authenticationDuration,
                            fpAuthRepository.authenticationStatus,
                            ::Pair
                        )
                        .onEach { (authDuration, authStatus) ->
                    sfpsSensorInteractor.authenticationDuration
                        .flatMapLatest { authDuration ->
                            _animator?.cancel()
                            fpAuthRepository.authenticationStatus.map { authStatus ->
                                when (authStatus) {
                                    is AcquiredFingerprintAuthenticationStatus -> {
                                        if (authStatus.fingerprintCaptureStarted) {
                                        _visible.value = true
                                            if (keyguardInteractor.isDozing.value) {
                                                dozeServiceHost.fireSideFpsAcquisitionStarted()
                                            } else {
                                                powerInteractor
                                                    .wakeUpForSideFingerprintAcquisition()
                                            }
                                            _animator?.cancel()
                                            _animator =
                                                ValueAnimator.ofFloat(0.0f, 1.0f)
                                                    .setDuration(authDuration)
                                                    .apply {
                                                        addUpdateListener {
                                                        _progress.value = it.animatedValue as Float
                                                            _progress.value =
                                                                it.animatedValue as Float
                                                        }
                                                        addListener(
                                                        doOnEnd {
                                                            onEnd = {
                                                                if (_progress.value == 0.0f) {
                                                                    _visible.value = false
                                                                }
                                                        }
                                                            },
                                                            onStart = { _visible.value = true },
                                                            onCancel = { _visible.value = false }
                                                        )
                                                    }
                                            _animator?.start()
@@ -220,6 +231,7 @@ constructor(
                                    else -> Unit
                                }
                            }
                        }
                        .flowOn(mainDispatcher)
                        .onCompletion { _animator?.cancel() }
                        .launchIn(applicationScope)
Loading