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

Commit 3aab3c96 authored by Alejandro Nijamkin's avatar Alejandro Nijamkin
Browse files

[flexiglass] Support for delayed locking

Depending on user settings like Screen Timeout, Lock after screen
timeout, and Power button instantly locks - locking the device after
it's put to sleep might or might not be instant. This CL adds support
for this.

Fix: 376759587
Test: manually tested a screen timeout of 15 seconds with a lock after
screen timeout of an additional 15 seconds - made sure that turning on
the screen within 15 seconds stays on the Gone scene
Test: manually tested a screen timeout of 15 seconds with a lock after
screen timeout of an additional 15 seconds - made sure that letting the
timeout occur correctly locks the device and that turning it back on
again reveals the Lockscreen scene
Test: manually tested that setting "power button instantly locks" means
just that
Test: manully tested with a non-secure auth method, even if there is a
previously-set non zero value for 'lock after screen timeout', it still
instantly locks
Test: unit tests added
Flag: com.android.systemui.scene_container

Change-Id: Idd2036d745dc2521bc080151e0a720a20b57c06e
parent 3516029b
Loading
Loading
Loading
Loading
+67 −0
Original line number Diff line number Diff line
@@ -17,6 +17,8 @@
package com.android.systemui.deviceentry.domain.interactor

import android.content.pm.UserInfo
import android.os.PowerManager
import android.provider.Settings
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.internal.widget.LockPatternUtils
@@ -41,9 +43,12 @@ import com.android.systemui.power.domain.interactor.powerInteractor
import com.android.systemui.testKosmos
import com.android.systemui.user.data.model.SelectionStatus
import com.android.systemui.user.data.repository.fakeUserRepository
import com.android.systemui.user.domain.interactor.selectedUserInteractor
import com.android.systemui.util.settings.fakeSettings
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.advanceTimeBy
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
@@ -225,6 +230,12 @@ class DeviceUnlockedInteractorTest : SysuiTestCase() {
    @Test
    fun deviceUnlockStatus_isResetToFalse_whenDeviceGoesToSleep() =
        testScope.runTest {
            kosmos.fakeSettings.putIntForUser(
                Settings.Secure.LOCK_SCREEN_LOCK_AFTER_TIMEOUT,
                0,
                kosmos.selectedUserInteractor.getSelectedUserId(),
            )
            kosmos.fakeAuthenticationRepository.powerButtonInstantlyLocks = false
            val deviceUnlockStatus by collectLastValue(underTest.deviceUnlockStatus)

            kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus(
@@ -239,9 +250,65 @@ class DeviceUnlockedInteractorTest : SysuiTestCase() {
            assertThat(deviceUnlockStatus?.isUnlocked).isFalse()
        }

    @Test
    fun deviceUnlockStatus_isResetToFalse_whenDeviceGoesToSleep_afterDelay() =
        testScope.runTest {
            val delay = 5000
            kosmos.fakeSettings.putIntForUser(
                Settings.Secure.LOCK_SCREEN_LOCK_AFTER_TIMEOUT,
                delay,
                kosmos.selectedUserInteractor.getSelectedUserId(),
            )
            kosmos.fakeAuthenticationRepository.powerButtonInstantlyLocks = false
            val deviceUnlockStatus by collectLastValue(underTest.deviceUnlockStatus)

            kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus(
                SuccessFingerprintAuthenticationStatus(0, true)
            )
            runCurrent()
            assertThat(deviceUnlockStatus?.isUnlocked).isTrue()

            kosmos.powerInteractor.setAsleepForTest()
            runCurrent()
            assertThat(deviceUnlockStatus?.isUnlocked).isTrue()

            advanceTimeBy(delay.toLong())
            assertThat(deviceUnlockStatus?.isUnlocked).isFalse()
        }

    @Test
    fun deviceUnlockStatus_isResetToFalse_whenDeviceGoesToSleep_powerButtonLocksInstantly() =
        testScope.runTest {
            kosmos.fakeSettings.putIntForUser(
                Settings.Secure.LOCK_SCREEN_LOCK_AFTER_TIMEOUT,
                5000,
                kosmos.selectedUserInteractor.getSelectedUserId(),
            )
            kosmos.fakeAuthenticationRepository.powerButtonInstantlyLocks = true
            val deviceUnlockStatus by collectLastValue(underTest.deviceUnlockStatus)

            kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus(
                SuccessFingerprintAuthenticationStatus(0, true)
            )
            runCurrent()
            assertThat(deviceUnlockStatus?.isUnlocked).isTrue()

            kosmos.powerInteractor.setAsleepForTest(
                sleepReason = PowerManager.GO_TO_SLEEP_REASON_POWER_BUTTON
            )
            runCurrent()

            assertThat(deviceUnlockStatus?.isUnlocked).isFalse()
        }

    @Test
    fun deviceUnlockStatus_becomesUnlocked_whenFingerprintUnlocked_whileDeviceAsleep() =
        testScope.runTest {
            kosmos.fakeSettings.putIntForUser(
                Settings.Secure.LOCK_SCREEN_LOCK_AFTER_TIMEOUT,
                0,
                kosmos.selectedUserInteractor.getSelectedUserId(),
            )
            val deviceUnlockStatus by collectLastValue(underTest.deviceUnlockStatus)
            assertThat(deviceUnlockStatus?.isUnlocked).isFalse()

+26 −25
Original line number Diff line number Diff line
@@ -34,6 +34,7 @@

package com.android.systemui.keyguard.domain.interactor

import android.provider.Settings
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
@@ -43,6 +44,7 @@ import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.coroutines.collectValues
import com.android.systemui.flags.DisableSceneContainer
import com.android.systemui.flags.EnableSceneContainer
import com.android.systemui.keyguard.KeyguardViewMediator
import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
import com.android.systemui.keyguard.data.repository.deviceEntryFingerprintAuthRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
@@ -60,10 +62,13 @@ import com.android.systemui.scene.data.repository.setSceneTransition
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.statusbar.domain.interactor.keyguardOcclusionInteractor
import com.android.systemui.testKosmos
import com.android.systemui.util.settings.data.repository.userAwareSecureSettingsRepository
import com.google.common.truth.Truth.assertThat
import junit.framework.Assert.assertFalse
import junit.framework.Assert.assertTrue
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.advanceTimeBy
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
@@ -96,7 +101,7 @@ class KeyguardOcclusionInteractorTest : SysuiTestCase() {
                from = KeyguardState.LOCKSCREEN,
                to = KeyguardState.AOD,
                testScope = testScope,
                throughTransitionState = TransitionState.RUNNING
                throughTransitionState = TransitionState.RUNNING,
            )

            powerInteractor.onCameraLaunchGestureDetected()
@@ -134,7 +139,7 @@ class KeyguardOcclusionInteractorTest : SysuiTestCase() {
                from = KeyguardState.AOD,
                to = KeyguardState.LOCKSCREEN,
                testScope = testScope,
                throughTransitionState = TransitionState.RUNNING
                throughTransitionState = TransitionState.RUNNING,
            )

            powerInteractor.onCameraLaunchGestureDetected()
@@ -182,21 +187,12 @@ class KeyguardOcclusionInteractorTest : SysuiTestCase() {
            kosmos.keyguardOcclusionRepository.setShowWhenLockedActivityInfo(true)
            runCurrent()

            assertThat(values)
                .containsExactly(
                    false,
                    true,
                )
            assertThat(values).containsExactly(false, true)

            kosmos.keyguardOcclusionRepository.setShowWhenLockedActivityInfo(false)
            runCurrent()

            assertThat(values)
                .containsExactly(
                    false,
                    true,
                    false,
                )
            assertThat(values).containsExactly(false, true, false)

            kosmos.keyguardOcclusionRepository.setShowWhenLockedActivityInfo(true)
            runCurrent()
@@ -228,7 +224,7 @@ class KeyguardOcclusionInteractorTest : SysuiTestCase() {
                from = KeyguardState.GONE,
                to = KeyguardState.AOD,
                testScope = testScope,
                throughTransitionState = TransitionState.RUNNING
                throughTransitionState = TransitionState.RUNNING,
            )

            powerInteractor.onCameraLaunchGestureDetected()
@@ -242,10 +238,7 @@ class KeyguardOcclusionInteractorTest : SysuiTestCase() {
                testScope = testScope,
            )

            assertThat(values)
                .containsExactly(
                    false,
                )
            assertThat(values).containsExactly(false)
        }

    @Test
@@ -263,7 +256,7 @@ class KeyguardOcclusionInteractorTest : SysuiTestCase() {
                from = KeyguardState.UNDEFINED,
                to = KeyguardState.AOD,
                testScope = testScope,
                throughTransitionState = TransitionState.RUNNING
                throughTransitionState = TransitionState.RUNNING,
            )

            powerInteractor.onCameraLaunchGestureDetected()
@@ -278,10 +271,7 @@ class KeyguardOcclusionInteractorTest : SysuiTestCase() {
                testScope = testScope,
            )

            assertThat(values)
                .containsExactly(
                    false,
                )
            assertThat(values).containsExactly(false)
        }

    @Test
@@ -304,8 +294,19 @@ class KeyguardOcclusionInteractorTest : SysuiTestCase() {
            assertThat(occludingActivityWillDismissKeyguard).isTrue()

            // Re-lock device:
            kosmos.powerInteractor.setAsleepForTest()
            runCurrent()
            lockDevice()
            assertThat(occludingActivityWillDismissKeyguard).isFalse()
        }

    private suspend fun TestScope.lockDevice() {
        kosmos.powerInteractor.setAsleepForTest()
        advanceTimeBy(
            kosmos.userAwareSecureSettingsRepository
                .getInt(
                    Settings.Secure.LOCK_SCREEN_LOCK_AFTER_TIMEOUT,
                    KeyguardViewMediator.KEYGUARD_LOCK_AFTER_DELAY_DEFAULT,
                )
                .toLong()
        )
    }
}
+25 −3
Original line number Diff line number Diff line
@@ -18,6 +18,7 @@

package com.android.systemui.scene

import android.provider.Settings
import android.telephony.TelephonyManager
import android.testing.TestableLooper.RunWithLooper
import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -42,6 +43,7 @@ import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
import com.android.systemui.flags.EnableSceneContainer
import com.android.systemui.flags.Flags
import com.android.systemui.flags.fakeFeatureFlagsClassic
import com.android.systemui.keyguard.KeyguardViewMediator
import com.android.systemui.keyguard.ui.viewmodel.lockscreenUserActionsViewModel
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.collectLastValue
@@ -64,6 +66,7 @@ import com.android.systemui.telephony.data.repository.fakeTelephonyRepository
import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.settings.data.repository.userAwareSecureSettingsRepository
import com.android.telecom.mockTelecomManager
import com.google.common.truth.Truth.assertThat
import com.google.common.truth.Truth.assertWithMessage
@@ -72,6 +75,7 @@ import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.launch
import kotlinx.coroutines.test.advanceTimeBy
import kotlinx.coroutines.test.runCurrent
import org.junit.Before
import org.junit.Test
@@ -541,7 +545,14 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() {
            .isTrue()

        powerInteractor.setAsleepForTest()
        testScope.runCurrent()
        testScope.advanceTimeBy(
            kosmos.userAwareSecureSettingsRepository
                .getInt(
                    Settings.Secure.LOCK_SCREEN_LOCK_AFTER_TIMEOUT,
                    KeyguardViewMediator.KEYGUARD_LOCK_AFTER_DELAY_DEFAULT,
                )
                .toLong()
        )

        powerInteractor.setAwakeForTest()
        testScope.runCurrent()
@@ -631,15 +642,26 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() {
    }

    /** Changes device wakefulness state from awake to asleep, going through intermediary states. */
    private fun Kosmos.putDeviceToSleep() {
    private suspend fun Kosmos.putDeviceToSleep(waitForLock: Boolean = true) {
        val wakefulnessModel = powerInteractor.detailedWakefulness.value
        assertWithMessage("Cannot put device to sleep as it's already asleep!")
            .that(wakefulnessModel.isAwake())
            .isTrue()

        powerInteractor.setAsleepForTest()
        if (waitForLock) {
            testScope.advanceTimeBy(
                kosmos.userAwareSecureSettingsRepository
                    .getInt(
                        Settings.Secure.LOCK_SCREEN_LOCK_AFTER_TIMEOUT,
                        KeyguardViewMediator.KEYGUARD_LOCK_AFTER_DELAY_DEFAULT,
                    )
                    .toLong()
            )
        } else {
            testScope.runCurrent()
        }
    }

    /** Emulates the dismissal of the IME (soft keyboard). */
    private fun Kosmos.dismissIme() {
+11 −1
Original line number Diff line number Diff line
@@ -23,6 +23,7 @@ import android.hardware.face.FaceManager
import android.os.PowerManager
import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
import android.provider.Settings
import android.view.Display
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
@@ -59,6 +60,7 @@ import com.android.systemui.flags.EnableSceneContainer
import com.android.systemui.haptics.msdl.fakeMSDLPlayer
import com.android.systemui.haptics.vibratorHelper
import com.android.systemui.keyevent.data.repository.fakeKeyEventRepository
import com.android.systemui.keyguard.KeyguardViewMediator
import com.android.systemui.keyguard.data.repository.biometricSettingsRepository
import com.android.systemui.keyguard.data.repository.deviceEntryFingerprintAuthRepository
import com.android.systemui.keyguard.data.repository.fakeBiometricSettingsRepository
@@ -106,6 +108,7 @@ import com.android.systemui.statusbar.policy.data.repository.fakeDeviceProvision
import com.android.systemui.statusbar.sysuiStatusBarStateController
import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.settings.data.repository.userAwareSecureSettingsRepository
import com.google.android.msdl.data.model.MSDLToken
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -1339,7 +1342,14 @@ class SceneContainerStartableTest : SysuiTestCase() {
            // Putting the device to sleep to lock it again, which shouldn't report another
            // successful unlock.
            kosmos.powerInteractor.setAsleepForTest()
            runCurrent()
            advanceTimeBy(
                kosmos.userAwareSecureSettingsRepository
                    .getInt(
                        Settings.Secure.LOCK_SCREEN_LOCK_AFTER_TIMEOUT,
                        KeyguardViewMediator.KEYGUARD_LOCK_AFTER_DELAY_DEFAULT,
                    )
                    .toLong()
            )
            // Verify that the startable changed the scene to Lockscreen because the device locked
            // following the sleep.
            assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
+24 −1
Original line number Diff line number Diff line
@@ -22,6 +22,7 @@ import android.annotation.UserIdInt
import android.app.admin.DevicePolicyManager
import android.content.IntentFilter
import android.os.UserHandle
import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.internal.widget.LockPatternUtils
import com.android.internal.widget.LockscreenCredential
import com.android.keyguard.KeyguardSecurityModel
@@ -57,7 +58,6 @@ import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onStart
import com.android.app.tracing.coroutines.launchTraced as launch
import kotlinx.coroutines.withContext

/** Defines interface for classes that can access authentication-related application state. */
@@ -178,6 +178,16 @@ interface AuthenticationRepository {
     * profile of an organization-owned device.
     */
    @UserIdInt suspend fun getProfileWithMinFailedUnlockAttemptsForWipe(): Int

    /**
     * Returns the device policy enforced maximum time to lock the device, in milliseconds. When the
     * device goes to sleep, this is the maximum time the device policy allows to wait before
     * locking the device, despite what the user setting might be set to.
     */
    suspend fun getMaximumTimeToLock(): Long

    /** Returns `true` if the power button should instantly lock the device, `false` otherwise. */
    suspend fun getPowerButtonInstantlyLocks(): Boolean
}

@SysUISingleton
@@ -324,6 +334,19 @@ constructor(
        }
    }

    override suspend fun getMaximumTimeToLock(): Long {
        return withContext(backgroundDispatcher) {
            devicePolicyManager.getMaximumTimeToLock(/* admin= */ null, selectedUserId)
        }
    }

    /** Returns `true` if the power button should instantly lock the device, `false` otherwise. */
    override suspend fun getPowerButtonInstantlyLocks(): Boolean {
        return withContext(backgroundDispatcher) {
            lockPatternUtils.getPowerButtonInstantlyLocks(selectedUserId)
        }
    }

    private val selectedUserId: Int
        @UserIdInt get() = userRepository.getSelectedUserInfo().id

Loading