Merge "Glitchy notifs on clock size change" into main
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
index a50cc8f..306f4ff 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
@@ -103,7 +103,6 @@
     private val smartspaceViewModel: KeyguardSmartspaceViewModel,
     private val lockscreenContentViewModel: LockscreenContentViewModel,
     private val lockscreenSceneBlueprintsLazy: Lazy<Set<LockscreenSceneBlueprint>>,
-    private val keyguardBlueprintViewBinder: KeyguardBlueprintViewBinder,
     private val clockInteractor: KeyguardClockInteractor,
     private val keyguardViewMediator: KeyguardViewMediator,
 ) : CoreStartable {
@@ -150,7 +149,7 @@
                 cs.connect(composeView.id, BOTTOM, PARENT_ID, BOTTOM)
                 keyguardRootView.addView(composeView)
             } else {
-                keyguardBlueprintViewBinder.bind(
+                KeyguardBlueprintViewBinder.bind(
                     keyguardRootView,
                     keyguardBlueprintViewModel,
                     keyguardClockViewModel,
@@ -197,12 +196,14 @@
             KeyguardRootViewBinder.bind(
                 keyguardRootView,
                 keyguardRootViewModel,
+                keyguardBlueprintViewModel,
                 configuration,
                 occludingAppDeviceEntryMessageViewModel,
                 chipbarCoordinator,
                 screenOffAnimationController,
                 shadeInteractor,
                 clockInteractor,
+                keyguardClockViewModel,
                 interactionJankMonitor,
                 deviceEntryHapticsInteractor,
                 vibratorHelper,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBlueprintViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBlueprintViewBinder.kt
index 8160335..bec8f3d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBlueprintViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBlueprintViewBinder.kt
@@ -17,17 +17,12 @@
 
 package com.android.systemui.keyguard.ui.binder
 
-import android.os.Handler
-import android.transition.Transition
-import android.transition.TransitionManager
 import android.util.Log
 import androidx.constraintlayout.widget.ConstraintLayout
 import androidx.constraintlayout.widget.ConstraintSet
 import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.repeatOnLifecycle
 import com.android.app.tracing.coroutines.launch
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.keyguard.KeyguardBottomAreaRefactor
 import com.android.systemui.keyguard.shared.model.KeyguardBlueprint
 import com.android.systemui.keyguard.ui.view.layout.blueprints.transitions.BaseBlueprintTransition
@@ -40,47 +35,9 @@
 import com.android.systemui.res.R
 import com.android.systemui.shared.R as sharedR
 import com.android.systemui.util.kotlin.pairwise
-import javax.inject.Inject
-import kotlin.math.max
 
-@SysUISingleton
-class KeyguardBlueprintViewBinder
-@Inject
-constructor(
-    @Main private val handler: Handler,
-) {
-    private var runningPriority = -1
-    private val runningTransitions = mutableSetOf<Transition>()
-    private val isTransitionRunning: Boolean
-        get() = runningTransitions.size > 0
-    private val transitionListener =
-        object : Transition.TransitionListener {
-            override fun onTransitionCancel(transition: Transition) {
-                if (DEBUG) Log.e(TAG, "onTransitionCancel: ${transition::class.simpleName}")
-                runningTransitions.remove(transition)
-            }
-
-            override fun onTransitionEnd(transition: Transition) {
-                if (DEBUG) Log.e(TAG, "onTransitionEnd: ${transition::class.simpleName}")
-                runningTransitions.remove(transition)
-            }
-
-            override fun onTransitionPause(transition: Transition) {
-                if (DEBUG) Log.i(TAG, "onTransitionPause: ${transition::class.simpleName}")
-                runningTransitions.remove(transition)
-            }
-
-            override fun onTransitionResume(transition: Transition) {
-                if (DEBUG) Log.i(TAG, "onTransitionResume: ${transition::class.simpleName}")
-                runningTransitions.add(transition)
-            }
-
-            override fun onTransitionStart(transition: Transition) {
-                if (DEBUG) Log.i(TAG, "onTransitionStart: ${transition::class.simpleName}")
-                runningTransitions.add(transition)
-            }
-        }
-
+object KeyguardBlueprintViewBinder {
+    @JvmStatic
     fun bind(
         constraintLayout: ConstraintLayout,
         viewModel: KeyguardBlueprintViewModel,
@@ -118,7 +75,7 @@
                                     )
                                 }
 
-                            runTransition(constraintLayout, transition, config) {
+                            viewModel.runTransition(constraintLayout, transition, config) {
                                 // Replace sections from the previous blueprint with the new ones
                                 blueprint.replaceViews(
                                     constraintLayout,
@@ -146,7 +103,7 @@
                     viewModel.refreshTransition.collect { config ->
                         val blueprint = viewModel.blueprint.value
 
-                        runTransition(
+                        viewModel.runTransition(
                             constraintLayout,
                             IntraBlueprintTransition(config, clockViewModel, smartspaceViewModel),
                             config,
@@ -167,50 +124,6 @@
         }
     }
 
-    private fun runTransition(
-        constraintLayout: ConstraintLayout,
-        transition: Transition,
-        config: Config,
-        apply: () -> Unit,
-    ) {
-        val currentPriority = if (isTransitionRunning) runningPriority else -1
-        if (config.checkPriority && config.type.priority < currentPriority) {
-            if (DEBUG) {
-                Log.w(
-                    TAG,
-                    "runTransition: skipping ${transition::class.simpleName}: " +
-                        "currentPriority=$currentPriority; config=$config"
-                )
-            }
-            apply()
-            return
-        }
-
-        if (DEBUG) {
-            Log.i(
-                TAG,
-                "runTransition: running ${transition::class.simpleName}: " +
-                    "currentPriority=$currentPriority; config=$config"
-            )
-        }
-
-        // beginDelayedTransition makes a copy, so we temporarially add the uncopied transition to
-        // the running set until the copy is started by the handler.
-        runningTransitions.add(transition)
-        transition.addListener(transitionListener)
-        runningPriority = max(currentPriority, config.type.priority)
-
-        handler.post {
-            if (config.terminatePrevious) {
-                TransitionManager.endTransitions(constraintLayout)
-            }
-
-            TransitionManager.beginDelayedTransition(constraintLayout, transition)
-            runningTransitions.remove(transition)
-            apply()
-        }
-    }
-
     private fun logAlphaVisibilityOfAppliedConstraintSet(
         cs: ConstraintSet,
         viewModel: KeyguardClockViewModel
@@ -237,8 +150,6 @@
         )
     }
 
-    companion object {
-        private const val TAG = "KeyguardBlueprintViewBinder"
-        private const val DEBUG = false
-    }
+    private const val TAG = "KeyguardBlueprintViewBinder"
+    private const val DEBUG = false
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
index 39db22d..fc92afe 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
@@ -22,6 +22,7 @@
 import android.annotation.SuppressLint
 import android.graphics.Point
 import android.graphics.Rect
+import android.util.Log
 import android.view.HapticFeedbackConstants
 import android.view.View
 import android.view.View.OnLayoutChangeListener
@@ -56,8 +57,11 @@
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.keyguard.ui.viewmodel.BurnInParameters
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardBlueprintViewModel
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel
 import com.android.systemui.keyguard.ui.viewmodel.OccludingAppDeviceEntryMessageViewModel
+import com.android.systemui.keyguard.ui.viewmodel.TransitionData
 import com.android.systemui.keyguard.ui.viewmodel.ViewStateAccessor
 import com.android.systemui.lifecycle.repeatWhenAttached
 import com.android.systemui.plugins.FalsingManager
@@ -93,12 +97,14 @@
     fun bind(
         view: ViewGroup,
         viewModel: KeyguardRootViewModel,
+        blueprintViewModel: KeyguardBlueprintViewModel,
         configuration: ConfigurationState,
         occludingAppDeviceEntryMessageViewModel: OccludingAppDeviceEntryMessageViewModel?,
         chipbarCoordinator: ChipbarCoordinator?,
         screenOffAnimationController: ScreenOffAnimationController,
         shadeInteractor: ShadeInteractor,
         clockInteractor: KeyguardClockInteractor,
+        clockViewModel: KeyguardClockViewModel,
         interactionJankMonitor: InteractionJankMonitor?,
         deviceEntryHapticsInteractor: DeviceEntryHapticsInteractor?,
         vibratorHelper: VibratorHelper?,
@@ -348,7 +354,16 @@
             }
         }
 
-        disposables += view.onLayoutChanged(OnLayoutChange(viewModel, childViews, burnInParams))
+        disposables +=
+            view.onLayoutChanged(
+                OnLayoutChange(
+                    viewModel,
+                    blueprintViewModel,
+                    clockViewModel,
+                    childViews,
+                    burnInParams
+                )
+            )
 
         // Views will be added or removed after the call to bind(). This is needed to avoid many
         // calls to findViewById
@@ -404,9 +419,13 @@
 
     private class OnLayoutChange(
         private val viewModel: KeyguardRootViewModel,
+        private val blueprintViewModel: KeyguardBlueprintViewModel,
+        private val clockViewModel: KeyguardClockViewModel,
         private val childViews: Map<Int, View>,
         private val burnInParams: MutableStateFlow<BurnInParameters>,
     ) : OnLayoutChangeListener {
+        var prevTransition: TransitionData? = null
+
         override fun onLayoutChange(
             view: View,
             left: Int,
@@ -418,11 +437,21 @@
             oldRight: Int,
             oldBottom: Int
         ) {
+            // After layout, ensure the notifications are positioned correctly
             childViews[nsslPlaceholderId]?.let { notificationListPlaceholder ->
-                // After layout, ensure the notifications are positioned correctly
+                // Do not update a second time while a blueprint transition is running
+                val transition = blueprintViewModel.currentTransition.value
+                val shouldAnimate = transition != null && transition.config.type.animateNotifChanges
+                if (prevTransition == transition && shouldAnimate) {
+                    if (DEBUG) Log.w(TAG, "Skipping; layout during transition")
+                    return
+                }
+
+                prevTransition = transition
                 viewModel.onNotificationContainerBoundsChanged(
                     notificationListPlaceholder.top.toFloat(),
                     notificationListPlaceholder.bottom.toFloat(),
+                    animate = shouldAnimate
                 )
             }
 
@@ -585,4 +614,6 @@
 
     private const val ID = "occluding_app_device_entry_unlock_msg"
     private const val AOD_ICONS_APPEAR_DURATION: Long = 200
+    private const val TAG = "KeyguardRootViewBinder"
+    private const val DEBUG = false
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
index fb1853f..777c873 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
@@ -68,7 +68,9 @@
 import com.android.systemui.keyguard.ui.binder.KeyguardRootViewBinder
 import com.android.systemui.keyguard.ui.view.KeyguardRootView
 import com.android.systemui.keyguard.ui.view.layout.sections.DefaultShortcutsSection
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardBlueprintViewModel
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardBottomAreaViewModel
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardPreviewClockViewModel
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardPreviewSmartspaceViewModel
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardQuickAffordancesCombinedViewModel
@@ -134,6 +136,7 @@
     private val vibratorHelper: VibratorHelper,
     private val indicationController: KeyguardIndicationController,
     private val keyguardRootViewModel: KeyguardRootViewModel,
+    private val keyguardBlueprintViewModel: KeyguardBlueprintViewModel,
     @Assisted bundle: Bundle,
     private val occludingAppDeviceEntryMessageViewModel: OccludingAppDeviceEntryMessageViewModel,
     private val chipbarCoordinator: ChipbarCoordinator,
@@ -143,6 +146,7 @@
     private val communalTutorialViewModel: CommunalTutorialIndicatorViewModel,
     private val defaultShortcutsSection: DefaultShortcutsSection,
     private val keyguardClockInteractor: KeyguardClockInteractor,
+    private val keyguardClockViewModel: KeyguardClockViewModel,
 ) {
     val hostToken: IBinder? = bundle.getBinder(KEY_HOST_TOKEN)
     private val width: Int = bundle.getInt(KEY_VIEW_WIDTH)
@@ -379,12 +383,14 @@
                 KeyguardRootViewBinder.bind(
                     keyguardRootView,
                     keyguardRootViewModel,
+                    keyguardBlueprintViewModel,
                     configuration,
                     occludingAppDeviceEntryMessageViewModel,
                     chipbarCoordinator,
                     screenOffAnimationController,
                     shadeInteractor,
                     keyguardClockInteractor,
+                    keyguardClockViewModel,
                     null, // jank monitor not required for preview mode
                     null, // device entry haptics not required preview mode
                     null, // device entry haptics not required for preview mode
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/transitions/IntraBlueprintTransition.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/transitions/IntraBlueprintTransition.kt
index 02e9ca5..39f1ebe 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/transitions/IntraBlueprintTransition.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/transitions/IntraBlueprintTransition.kt
@@ -31,16 +31,16 @@
 
     enum class Type(
         val priority: Int,
+        val animateNotifChanges: Boolean,
     ) {
-        ClockSize(100),
-        ClockCenter(99),
-        DefaultClockStepping(98),
-        AodNotifIconsTransition(97),
-        SmartspaceVisibility(2),
-        DefaultTransition(1),
+        ClockSize(100, true),
+        ClockCenter(99, false),
+        DefaultClockStepping(98, false),
+        SmartspaceVisibility(2, true),
+        DefaultTransition(1, false),
         // When transition between blueprint, we don't need any duration or interpolator but we need
         // all elements go to correct state
-        NoTransition(0),
+        NoTransition(0, false),
     }
 
     data class Config(
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodNotificationIconsSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodNotificationIconsSection.kt
index 2832e9d..d77b548 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodNotificationIconsSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodNotificationIconsSection.kt
@@ -19,6 +19,8 @@
 
 import android.content.Context
 import android.view.View
+import android.view.View.GONE
+import android.view.View.VISIBLE
 import androidx.constraintlayout.widget.ConstraintLayout
 import androidx.constraintlayout.widget.ConstraintSet
 import androidx.constraintlayout.widget.ConstraintSet.BOTTOM
@@ -29,6 +31,7 @@
 import com.android.systemui.common.ui.ConfigurationState
 import com.android.systemui.keyguard.MigrateClocksToBlueprint
 import com.android.systemui.keyguard.shared.model.KeyguardSection
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel
 import com.android.systemui.res.R
 import com.android.systemui.statusbar.notification.icon.ui.viewbinder.AlwaysOnDisplayNotificationIconViewStore
 import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerViewBinder
@@ -38,6 +41,7 @@
 import com.android.systemui.statusbar.phone.NotificationIconAreaController
 import com.android.systemui.statusbar.phone.NotificationIconContainer
 import com.android.systemui.statusbar.ui.SystemBarUtilsState
+import com.android.systemui.util.ui.value
 import javax.inject.Inject
 import kotlinx.coroutines.DisposableHandle
 
@@ -51,6 +55,7 @@
     private val nicAodIconViewStore: AlwaysOnDisplayNotificationIconViewStore,
     private val notificationIconAreaController: NotificationIconAreaController,
     private val systemBarUtilsState: SystemBarUtilsState,
+    private val rootViewModel: KeyguardRootViewModel,
 ) : KeyguardSection() {
 
     private var nicBindingDisposable: DisposableHandle? = null
@@ -101,20 +106,14 @@
         if (!MigrateClocksToBlueprint.isEnabled) {
             return
         }
+
         val bottomMargin =
             context.resources.getDimensionPixelSize(R.dimen.keyguard_status_view_bottom_margin)
-
-        val useSplitShade = context.resources.getBoolean(R.bool.config_use_split_notification_shade)
-
-        val topAlignment =
-            if (useSplitShade) {
-                TOP
-            } else {
-                BOTTOM
-            }
+        val isVisible = rootViewModel.isNotifIconContainerVisible.value
         constraintSet.apply {
             connect(nicId, TOP, R.id.smart_space_barrier_bottom, BOTTOM, bottomMargin)
             setGoneMargin(nicId, BOTTOM, bottomMargin)
+            setVisibility(nicId, if (isVisible.value) VISIBLE else GONE)
 
             connect(
                 nicId,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/transitions/ClockSizeTransition.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/transitions/ClockSizeTransition.kt
index 7c745bc..f17dbd2 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/transitions/ClockSizeTransition.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/transitions/ClockSizeTransition.kt
@@ -369,6 +369,21 @@
             addTarget(R.id.status_view_media_container)
         }
 
+        override fun mutateBounds(
+            view: View,
+            fromIsVis: Boolean,
+            toIsVis: Boolean,
+            fromBounds: Rect,
+            toBounds: Rect,
+            fromSSBounds: Rect?,
+            toSSBounds: Rect?
+        ) {
+            // If view is changing visibility, hold it in place
+            if (fromIsVis == toIsVis) return
+            if (DEBUG) Log.i(TAG, "Holding position of ${view.id}")
+            toBounds.set(fromBounds)
+        }
+
         companion object {
             const val STATUS_AREA_MOVE_UP_MILLIS = 967L
             const val STATUS_AREA_MOVE_DOWN_MILLIS = 467L
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBlueprintViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBlueprintViewModel.kt
index b1f1898..7ac03bf 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBlueprintViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBlueprintViewModel.kt
@@ -17,15 +17,119 @@
 
 package com.android.systemui.keyguard.ui.viewmodel
 
+import android.os.Handler
+import android.transition.Transition
+import android.transition.TransitionManager
+import android.util.Log
+import androidx.constraintlayout.widget.ConstraintLayout
+import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.keyguard.domain.interactor.KeyguardBlueprintInteractor
+import com.android.systemui.keyguard.ui.view.layout.blueprints.transitions.IntraBlueprintTransition.Config
 import javax.inject.Inject
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+data class TransitionData(
+    val config: Config,
+    val start: Long = System.currentTimeMillis(),
+)
 
 class KeyguardBlueprintViewModel
 @Inject
 constructor(
+    @Main private val handler: Handler,
     keyguardBlueprintInteractor: KeyguardBlueprintInteractor,
 ) {
     val blueprint = keyguardBlueprintInteractor.blueprint
     val blueprintId = keyguardBlueprintInteractor.blueprintId
     val refreshTransition = keyguardBlueprintInteractor.refreshTransition
+
+    private val _currentTransition = MutableStateFlow<TransitionData?>(null)
+    val currentTransition = _currentTransition.asStateFlow()
+
+    private val runningTransitions = mutableSetOf<Transition>()
+    private val transitionListener =
+        object : Transition.TransitionListener {
+            override fun onTransitionCancel(transition: Transition) {
+                if (DEBUG) Log.e(TAG, "onTransitionCancel: ${transition::class.simpleName}")
+                updateTransitions(null) { remove(transition) }
+            }
+
+            override fun onTransitionEnd(transition: Transition) {
+                if (DEBUG) Log.e(TAG, "onTransitionEnd: ${transition::class.simpleName}")
+                updateTransitions(null) { remove(transition) }
+            }
+
+            override fun onTransitionPause(transition: Transition) {
+                if (DEBUG) Log.i(TAG, "onTransitionPause: ${transition::class.simpleName}")
+                updateTransitions(null) { remove(transition) }
+            }
+
+            override fun onTransitionResume(transition: Transition) {
+                if (DEBUG) Log.i(TAG, "onTransitionResume: ${transition::class.simpleName}")
+                updateTransitions(null) { add(transition) }
+            }
+
+            override fun onTransitionStart(transition: Transition) {
+                if (DEBUG) Log.i(TAG, "onTransitionStart: ${transition::class.simpleName}")
+                updateTransitions(null) { add(transition) }
+            }
+        }
+
+    fun updateTransitions(data: TransitionData?, mutate: MutableSet<Transition>.() -> Unit) {
+        runningTransitions.mutate()
+
+        if (runningTransitions.size <= 0) _currentTransition.value = null
+        else if (data != null) _currentTransition.value = data
+    }
+
+    fun runTransition(
+        constraintLayout: ConstraintLayout,
+        transition: Transition,
+        config: Config,
+        apply: () -> Unit,
+    ) {
+        val currentPriority = currentTransition.value?.let { it.config.type.priority } ?: -1
+        if (config.checkPriority && config.type.priority < currentPriority) {
+            if (DEBUG) {
+                Log.w(
+                    TAG,
+                    "runTransition: skipping ${transition::class.simpleName}: " +
+                        "currentPriority=$currentPriority; config=$config"
+                )
+            }
+            apply()
+            return
+        }
+
+        if (DEBUG) {
+            Log.i(
+                TAG,
+                "runTransition: running ${transition::class.simpleName}: " +
+                    "currentPriority=$currentPriority; config=$config"
+            )
+        }
+
+        // beginDelayedTransition makes a copy, so we temporarially add the uncopied transition to
+        // the running set until the copy is started by the handler.
+        updateTransitions(TransitionData(config)) { add(transition) }
+        transition.addListener(transitionListener)
+
+        handler.post {
+            if (config.terminatePrevious) {
+                TransitionManager.endTransitions(constraintLayout)
+            }
+
+            TransitionManager.beginDelayedTransition(constraintLayout, transition)
+            apply()
+
+            // Delay removal until after copied transition has started
+            handler.post { updateTransitions(null) { remove(transition) } }
+        }
+    }
+
+    companion object {
+        private const val TAG = "KeyguardBlueprintViewModel"
+        private const val DEBUG = true
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
index aaec69f..1ec2a49 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
@@ -58,6 +58,8 @@
 import kotlinx.coroutines.Job
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.asStateFlow
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.combineTransform
@@ -67,13 +69,14 @@
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.merge
 import kotlinx.coroutines.flow.onStart
+import kotlinx.coroutines.flow.stateIn
 
 @OptIn(ExperimentalCoroutinesApi::class)
 @SysUISingleton
 class KeyguardRootViewModel
 @Inject
 constructor(
-    @Application private val scope: CoroutineScope,
+    @Application private val applicationScope: CoroutineScope,
     private val deviceEntryInteractor: DeviceEntryInteractor,
     private val dozeParameters: DozeParameters,
     private val keyguardInteractor: KeyguardInteractor,
@@ -280,7 +283,7 @@
         burnInJob?.cancel()
 
         burnInJob =
-            scope.launch("$TAG#aodBurnInViewModel") {
+            applicationScope.launch("$TAG#aodBurnInViewModel") {
                 aodBurnInViewModel.movement(params).collect { _burnInModel.value = it }
             }
     }
@@ -294,7 +297,7 @@
         }
 
     /** Is the notification icon container visible? */
-    val isNotifIconContainerVisible: Flow<AnimatedValue<Boolean>> =
+    val isNotifIconContainerVisible: StateFlow<AnimatedValue<Boolean>> =
         combine(
                 goneToAodTransitionRunning,
                 keyguardTransitionInteractor.finishedKeyguardState.map {
@@ -336,11 +339,15 @@
                         }
                 }
             }
-            .distinctUntilChanged()
+            .stateIn(
+                scope = applicationScope,
+                started = SharingStarted.WhileSubscribed(),
+                initialValue = AnimatedValue.NotAnimating(false),
+            )
 
-    fun onNotificationContainerBoundsChanged(top: Float, bottom: Float) {
+    fun onNotificationContainerBoundsChanged(top: Float, bottom: Float, animate: Boolean = false) {
         keyguardInteractor.setNotificationContainerBounds(
-            NotificationContainerBounds(top = top, bottom = bottom)
+            NotificationContainerBounds(top = top, bottom = bottom, isAnimated = animate)
         )
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBlueprintViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBlueprintViewModelTest.kt
index a18b033..ec2a1d3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBlueprintViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBlueprintViewModelTest.kt
@@ -17,9 +17,11 @@
 
 package com.android.systemui.keyguard.ui.viewmodel
 
+import android.os.fakeExecutorHandler
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.keyguard.domain.interactor.KeyguardBlueprintInteractor
+import com.android.systemui.testKosmos
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -33,12 +35,16 @@
 class KeyguardBlueprintViewModelTest : SysuiTestCase() {
     @Mock private lateinit var keyguardBlueprintInteractor: KeyguardBlueprintInteractor
     private lateinit var undertest: KeyguardBlueprintViewModel
+    private val kosmos = testKosmos()
 
     @Before
     fun setup() {
         MockitoAnnotations.initMocks(this)
         undertest =
-            KeyguardBlueprintViewModel(keyguardBlueprintInteractor = keyguardBlueprintInteractor)
+            KeyguardBlueprintViewModel(
+                handler = kosmos.fakeExecutorHandler,
+                keyguardBlueprintInteractor = keyguardBlueprintInteractor,
+            )
     }
 
     @Test
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/binder/KeyguardBlueprintViewBinderKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/binder/KeyguardBlueprintViewBinderKosmos.kt
deleted file mode 100644
index 24d2c2f..0000000
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/binder/KeyguardBlueprintViewBinderKosmos.kt
+++ /dev/null
@@ -1,23 +0,0 @@
-/*
- * Copyright (C) 2023 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.binder
-
-import android.os.fakeExecutorHandler
-import com.android.systemui.kosmos.Kosmos
-
-val Kosmos.keyguardBlueprintViewBinder by
-    Kosmos.Fixture { KeyguardBlueprintViewBinder(fakeExecutorHandler) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBlueprintViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBlueprintViewModelKosmos.kt
index 63b87c0..0c538ff 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBlueprintViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBlueprintViewModelKosmos.kt
@@ -16,8 +16,14 @@
 
 package com.android.systemui.keyguard.ui.viewmodel
 
+import android.os.fakeExecutorHandler
 import com.android.systemui.keyguard.domain.interactor.keyguardBlueprintInteractor
 import com.android.systemui.kosmos.Kosmos
 
 val Kosmos.keyguardBlueprintViewModel by
-    Kosmos.Fixture { KeyguardBlueprintViewModel(keyguardBlueprintInteractor) }
+    Kosmos.Fixture {
+        KeyguardBlueprintViewModel(
+            fakeExecutorHandler,
+            keyguardBlueprintInteractor,
+        )
+    }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt
index f856d27..2567ffe 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt
@@ -32,7 +32,7 @@
 
 val Kosmos.keyguardRootViewModel by Fixture {
     KeyguardRootViewModel(
-        scope = applicationCoroutineScope,
+        applicationScope = applicationCoroutineScope,
         deviceEntryInteractor = deviceEntryInteractor,
         dozeParameters = dozeParameters,
         keyguardInteractor = keyguardInteractor,