Add KeyguardTransitionBootInteractor.
We previously always initialized with OFF -> LOCKSCREEN, however, there are various cases (including SUW) where this is not correct, and we should be in GONE. This uses the logic from KeyguardViewMediator#setupLocked to decide whether to start in LS or GONE.
Fixes: 327279527
Test: atest SystemUITests
Test: wipe device and confirm that we init in GONE; finish SUW/reboot/verify we're in LS
Flag: NA
Change-Id: I87d38a2db9c64b78ea677750ad83d9b6d0d14801
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt
index 41229255..bf0939c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt
@@ -235,7 +235,13 @@
.isEqualTo(
listOf(
// The initial transition will also get sent when collect started
- TransitionStep(OFF, LOCKSCREEN, 0f, STARTED),
+ TransitionStep(
+ OFF,
+ LOCKSCREEN,
+ 0f,
+ STARTED,
+ ownerName = "KeyguardTransitionRepository(boot)"
+ ),
steps[0],
steps[3],
steps[6]
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt
index 662974d..d079a95 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt
@@ -240,6 +240,15 @@
}
/**
+ * Whether the lockscreen is enabled for the current user. This is `true` whenever the user has
+ * chosen any secure authentication method and even if they set the lockscreen to be dismissed
+ * when the user swipes on it.
+ */
+ suspend fun isLockscreenEnabled(): Boolean {
+ return repository.isLockscreenEnabled()
+ }
+
+ /**
* Whether lockscreen bypass is enabled. When enabled, the lockscreen will be automatically
* dismissed once the authentication challenge is completed. For example, completing a biometric
* authentication challenge via face unlock or fingerprint sensor can automatically bypass the
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt
index 4c54bfd..e32bfcf 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt
@@ -89,6 +89,12 @@
suspend fun startTransition(info: TransitionInfo): UUID?
/**
+ * Emits STARTED and FINISHED transition steps to the given state. This is used during boot to
+ * seed the repository with the appropriate initial state.
+ */
+ suspend fun emitInitialStepsFromOff(to: KeyguardState)
+
+ /**
* Allows manual control of a transition. When calling [startTransition], the consumer must pass
* in a null animator. In return, it will get a unique [UUID] that will be validated to allow
* further updates.
@@ -141,9 +147,17 @@
private var updateTransitionId: UUID? = null
init {
- // Seed with transitions signaling a boot into lockscreen state. If updating this, please
- // also update FakeKeyguardTransitionRepository.
- initialTransitionSteps.forEach(::emitTransition)
+ // Start with a FINISHED transition in OFF. KeyguardBootInteractor will transition from OFF
+ // to either GONE or LOCKSCREEN once we're booted up and can determine which state we should
+ // start in.
+ emitTransition(
+ TransitionStep(
+ KeyguardState.OFF,
+ KeyguardState.OFF,
+ 1f,
+ TransitionState.FINISHED,
+ )
+ )
}
override suspend fun startTransition(info: TransitionInfo): UUID? {
@@ -251,6 +265,28 @@
lastStep = nextStep
}
+ override suspend fun emitInitialStepsFromOff(to: KeyguardState) {
+ emitTransition(
+ TransitionStep(
+ KeyguardState.OFF,
+ to,
+ 0f,
+ TransitionState.STARTED,
+ ownerName = "KeyguardTransitionRepository(boot)",
+ )
+ )
+
+ emitTransition(
+ TransitionStep(
+ KeyguardState.OFF,
+ to,
+ 1f,
+ TransitionState.FINISHED,
+ ownerName = "KeyguardTransitionRepository(boot)",
+ ),
+ )
+ }
+
private fun logAndTrace(step: TransitionStep, isManual: Boolean) {
if (step.transitionState == TransitionState.RUNNING) {
return
@@ -271,31 +307,5 @@
companion object {
private const val TAG = "KeyguardTransitionRepository"
-
- /**
- * Transition steps to seed the repository with, so that all of the transition interactor
- * flows emit reasonable initial values.
- */
- val initialTransitionSteps: List<TransitionStep> =
- listOf(
- TransitionStep(
- KeyguardState.OFF,
- KeyguardState.OFF,
- 1f,
- TransitionState.FINISHED,
- ),
- TransitionStep(
- KeyguardState.OFF,
- KeyguardState.LOCKSCREEN,
- 0f,
- TransitionState.STARTED,
- ),
- TransitionStep(
- KeyguardState.OFF,
- KeyguardState.LOCKSCREEN,
- 1f,
- TransitionState.FINISHED,
- ),
- )
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
index 2eeb3b9..115fc36 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
@@ -66,7 +66,7 @@
listenForTransitionToCamera(scope, keyguardInteractor)
}
- private val canDismissLockScreen: Flow<Boolean> =
+ private val canTransitionToGoneOnWake: Flow<Boolean> =
combine(
keyguardInteractor.isKeyguardShowing,
keyguardInteractor.isKeyguardDismissible,
@@ -87,7 +87,7 @@
keyguardInteractor.biometricUnlockState,
keyguardInteractor.isKeyguardOccluded,
communalInteractor.isIdleOnCommunal,
- canDismissLockScreen,
+ canTransitionToGoneOnWake,
keyguardInteractor.primaryBouncerShowing,
)
.collect {
@@ -96,12 +96,12 @@
biometricUnlockState,
occluded,
isIdleOnCommunal,
- canDismissLockScreen,
+ canTransitionToGoneOnWake,
primaryBouncerShowing) ->
startTransitionTo(
if (isWakeAndUnlock(biometricUnlockState.mode)) {
KeyguardState.GONE
- } else if (canDismissLockScreen) {
+ } else if (canTransitionToGoneOnWake) {
KeyguardState.GONE
} else if (primaryBouncerShowing) {
KeyguardState.PRIMARY_BOUNCER
@@ -129,7 +129,7 @@
.sample(
communalInteractor.isIdleOnCommunal,
keyguardInteractor.biometricUnlockState,
- canDismissLockScreen,
+ canTransitionToGoneOnWake,
keyguardInteractor.primaryBouncerShowing,
)
.collect {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionBootInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionBootInteractor.kt
new file mode 100644
index 0000000..5ad7762
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionBootInteractor.kt
@@ -0,0 +1,81 @@
+/*
+ * 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.domain.interactor
+
+import android.util.Log
+import com.android.systemui.CoreStartable
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
+import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.statusbar.policy.domain.interactor.DeviceProvisioningInteractor
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.launch
+
+/** Handles initialization of the KeyguardTransitionRepository on boot. */
+@SysUISingleton
+class KeyguardTransitionBootInteractor
+@Inject
+constructor(
+ @Application val scope: CoroutineScope,
+ val deviceEntryInteractor: DeviceEntryInteractor,
+ val deviceProvisioningInteractor: DeviceProvisioningInteractor,
+ val keyguardTransitionInteractor: KeyguardTransitionInteractor,
+ val repository: KeyguardTransitionRepository,
+) : CoreStartable {
+
+ /**
+ * Whether the lockscreen should be showing when the device starts up for the first time. If not
+ * then we'll seed the repository with a transition from OFF -> GONE.
+ */
+ @OptIn(ExperimentalCoroutinesApi::class)
+ private val showLockscreenOnBoot =
+ deviceProvisioningInteractor.isDeviceProvisioned.map { provisioned ->
+ (provisioned || deviceEntryInteractor.isAuthenticationRequired()) &&
+ deviceEntryInteractor.isLockscreenEnabled()
+ }
+
+ override fun start() {
+ scope.launch {
+ val state =
+ if (showLockscreenOnBoot.first()) {
+ KeyguardState.LOCKSCREEN
+ } else {
+ KeyguardState.GONE
+ }
+
+ if (
+ keyguardTransitionInteractor.currentTransitionInfoInternal.value.from !=
+ KeyguardState.OFF
+ ) {
+ Log.e(
+ "KeyguardTransitionInteractor",
+ "showLockscreenOnBoot emitted, but we've already " +
+ "transitioned to a state other than OFF. We'll respect that " +
+ "transition, but this should not happen."
+ )
+ } else {
+ repository.emitInitialStepsFromOff(state)
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionCoreStartable.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionCoreStartable.kt
index 91f8420..31b0bf7 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionCoreStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionCoreStartable.kt
@@ -27,6 +27,7 @@
constructor(
private val interactors: Set<TransitionInteractor>,
private val auditLogger: KeyguardTransitionAuditLogger,
+ private val bootInteractor: KeyguardTransitionBootInteractor,
) : CoreStartable {
override fun start() {
@@ -51,6 +52,7 @@
it.start()
}
auditLogger.start()
+ bootInteractor.start()
}
companion object {
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardTransitionRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardTransitionRepository.kt
index a242368..2fe7438 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardTransitionRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardTransitionRepository.kt
@@ -40,12 +40,21 @@
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
-/** Fake implementation of [KeyguardTransitionRepository] */
+/**
+ * Fake implementation of [KeyguardTransitionRepository].
+ *
+ * By default, will be seeded with a transition from OFF -> LOCKSCREEN, which is the most common
+ * case. If the lockscreen is disabled, or we're in setup wizard, the repository will initialize
+ * with OFF -> GONE. Construct with initInLockscreen = false if your test requires this behavior.
+ */
@SysUISingleton
-class FakeKeyguardTransitionRepository @Inject constructor() : KeyguardTransitionRepository {
+class FakeKeyguardTransitionRepository(
+ private val initInLockscreen: Boolean = true,
+) : KeyguardTransitionRepository {
private val _transitions =
MutableSharedFlow<TransitionStep>(replay = 3, onBufferOverflow = BufferOverflow.DROP_OLDEST)
override val transitions: SharedFlow<TransitionStep> = _transitions
+ @Inject constructor() : this(initInLockscreen = true)
private val _currentTransitionInfo: MutableStateFlow<TransitionInfo> =
MutableStateFlow(
@@ -59,8 +68,21 @@
override var currentTransitionInfoInternal = _currentTransitionInfo.asStateFlow()
init {
- // Seed the fake repository with the same initial steps the actual repository uses.
- KeyguardTransitionRepositoryImpl.initialTransitionSteps.forEach { _transitions.tryEmit(it) }
+ // Seed with a FINISHED transition in OFF, same as the real repository.
+ _transitions.tryEmit(
+ TransitionStep(
+ KeyguardState.OFF,
+ KeyguardState.OFF,
+ 1f,
+ TransitionState.FINISHED,
+ )
+ )
+
+ if (initInLockscreen) {
+ tryEmitInitialStepsFromOff(KeyguardState.LOCKSCREEN)
+ } else {
+ tryEmitInitialStepsFromOff(KeyguardState.OFF)
+ }
}
/**
@@ -223,6 +245,32 @@
return if (info.animator == null) UUID.randomUUID() else null
}
+ override suspend fun emitInitialStepsFromOff(to: KeyguardState) {
+ tryEmitInitialStepsFromOff(to)
+ }
+
+ private fun tryEmitInitialStepsFromOff(to: KeyguardState) {
+ _transitions.tryEmit(
+ TransitionStep(
+ KeyguardState.OFF,
+ to,
+ 0f,
+ TransitionState.STARTED,
+ ownerName = "KeyguardTransitionRepository(boot)",
+ )
+ )
+
+ _transitions.tryEmit(
+ TransitionStep(
+ KeyguardState.OFF,
+ to,
+ 1f,
+ TransitionState.FINISHED,
+ ownerName = "KeyguardTransitionRepository(boot)",
+ ),
+ )
+ }
+
override fun updateTransition(
transitionId: UUID,
@FloatRange(from = 0.0, to = 1.0) value: Float,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionBootInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionBootInteractorKosmos.kt
new file mode 100644
index 0000000..7d8d33f
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionBootInteractorKosmos.kt
@@ -0,0 +1,34 @@
+/*
+ * 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.domain.interactor
+
+import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
+import com.android.systemui.keyguard.data.repository.keyguardTransitionRepository
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.statusbar.policy.domain.interactor.deviceProvisioningInteractor
+
+val Kosmos.keyguardTransitionBootInteractor: KeyguardTransitionBootInteractor by
+ Kosmos.Fixture {
+ KeyguardTransitionBootInteractor(
+ scope = applicationCoroutineScope,
+ deviceEntryInteractor = deviceEntryInteractor,
+ deviceProvisioningInteractor = deviceProvisioningInteractor,
+ keyguardTransitionInteractor = keyguardTransitionInteractor,
+ repository = keyguardTransitionRepository,
+ )
+ }