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

Commit 6d131843 authored by Chandru S's avatar Chandru S Committed by Android (Google) Code Review
Browse files

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

parents e139472a 73a89177
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