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,
+        )
+    }