diff --git a/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt b/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt
index 82d943796e2a8666c51e56b3cb10262a02a0b076..a02c5ce8bb14af32b16af1bcd3eae3ad357204b8 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt
@@ -23,6 +23,8 @@ import com.android.systemui.communal.data.repository.CommunalRepositoryModule
 import com.android.systemui.communal.data.repository.CommunalSettingsRepositoryModule
 import com.android.systemui.communal.data.repository.CommunalTutorialRepositoryModule
 import com.android.systemui.communal.data.repository.CommunalWidgetRepositoryModule
+import com.android.systemui.communal.ui.viewmodel.CommunalTransitionViewModel
+import com.android.systemui.communal.ui.viewmodel.CommunalTransitionViewModelImpl
 import com.android.systemui.communal.widgets.CommunalWidgetModule
 import com.android.systemui.communal.widgets.EditWidgetsActivityStarter
 import com.android.systemui.communal.widgets.EditWidgetsActivityStarterImpl
@@ -47,4 +49,9 @@ interface CommunalModule {
     fun bindEditWidgetsActivityStarter(
         starter: EditWidgetsActivityStarterImpl
     ): EditWidgetsActivityStarter
+
+    @Binds
+    fun bindCommunalTransitionViewModel(
+        impl: CommunalTransitionViewModelImpl
+    ): CommunalTransitionViewModel
 }
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModel.kt
new file mode 100644
index 0000000000000000000000000000000000000000..eed0aa397b65d14c49cdd9c94f857b21e7b05248
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModel.kt
@@ -0,0 +1,58 @@
+/*
+ * 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.communal.ui.viewmodel
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.ui.viewmodel.DreamingToGlanceableHubTransitionViewModel
+import com.android.systemui.keyguard.ui.viewmodel.GlanceableHubToDreamingTransitionViewModel
+import com.android.systemui.keyguard.ui.viewmodel.GlanceableHubToLockscreenTransitionViewModel
+import com.android.systemui.keyguard.ui.viewmodel.LockscreenToGlanceableHubTransitionViewModel
+import javax.inject.Inject
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.merge
+
+/** View model for transitions related to the communal hub. */
+interface CommunalTransitionViewModel {
+    val isUmoOnCommunal: Flow<Boolean>
+}
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SysUISingleton
+class CommunalTransitionViewModelImpl
+@Inject
+constructor(
+    glanceableHubToLockscreenTransitionViewModel: GlanceableHubToLockscreenTransitionViewModel,
+    lockscreenToGlanceableHubTransitionViewModel: LockscreenToGlanceableHubTransitionViewModel,
+    dreamToGlanceableHubTransitionViewModel: DreamingToGlanceableHubTransitionViewModel,
+    glanceableHubToDreamTransitionViewModel: GlanceableHubToDreamingTransitionViewModel,
+) : CommunalTransitionViewModel {
+    /**
+     * Whether UMO location should be on communal. This flow is responsive to transitions so that a
+     * new value is emitted at the right step of a transition to/from communal hub that the location
+     * of UMO should be updated.
+     */
+    override val isUmoOnCommunal: Flow<Boolean> =
+        merge(
+                lockscreenToGlanceableHubTransitionViewModel.showUmo,
+                glanceableHubToLockscreenTransitionViewModel.showUmo,
+                dreamToGlanceableHubTransitionViewModel.showUmo,
+                glanceableHubToDreamTransitionViewModel.showUmo,
+            )
+            .distinctUntilChanged()
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToGlanceableHubTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToGlanceableHubTransitionViewModel.kt
index c64f277b519a4338497feef1ccfdc726ff38b8da..5cb2ec9642b9c1c7bfc05ffc3dcc3d2bc8de6908 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToGlanceableHubTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToGlanceableHubTransitionViewModel.kt
@@ -28,6 +28,7 @@ import kotlin.time.Duration.Companion.seconds
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.map
 
 @OptIn(ExperimentalCoroutinesApi::class)
 @SysUISingleton
@@ -65,6 +66,15 @@ constructor(
             name = "DREAMING->GLANCEABLE_HUB: dreamOverlayAlpha",
         )
 
+    // Show UMO once the transition starts.
+    val showUmo: Flow<Boolean> =
+        transitionAnimation
+            .sharedFlow(
+                duration = TO_GLANCEABLE_HUB_DURATION,
+                onStep = { it },
+            )
+            .map { step -> step != 0f }
+
     private companion object {
         val TO_GLANCEABLE_HUB_DURATION = 1.seconds
     }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToDreamingTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToDreamingTransitionViewModel.kt
index 478c4faa1be303fd7fa1b09a1afc5c5d9cf17e3f..90be4f90410544249636a8a082c4c50e25599907 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToDreamingTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToDreamingTransitionViewModel.kt
@@ -28,6 +28,7 @@ import kotlin.time.Duration.Companion.seconds
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.map
 
 @OptIn(ExperimentalCoroutinesApi::class)
 @SysUISingleton
@@ -66,6 +67,15 @@ constructor(
                 )
             }
 
+    // Show UMO until transition finishes.
+    val showUmo: Flow<Boolean> =
+        transitionAnimation
+            .sharedFlow(
+                duration = FROM_GLANCEABLE_HUB_DURATION,
+                onStep = { it },
+            )
+            .map { step -> step != 1f }
+
     private companion object {
         val FROM_GLANCEABLE_HUB_DURATION = 1.seconds
     }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToLockscreenTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToLockscreenTransitionViewModel.kt
index e5b596419efe34cce2fb79c6bffff402df88bbc6..f81598f717b07749a9744d8a51d0795096e52f89 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToLockscreenTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToLockscreenTransitionViewModel.kt
@@ -64,6 +64,9 @@ constructor(
             )
             .onStart { emit(0f) }
 
+    // Show UMO as long as keyguard is not visible.
+    val showUmo: Flow<Boolean> = keyguardAlpha.map { alpha -> alpha == 0f }
+
     val keyguardTranslationX: Flow<StateToValue> =
         configurationInteractor
             .dimensionPixelSize(R.dimen.hub_to_lockscreen_transition_lockscreen_translation_x)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGlanceableHubTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGlanceableHubTransitionViewModel.kt
index 978e71e2a825e759fac1931db2c9426645bae041..0c33e18d0113afd947c4ab267423f28c63ba35f8 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGlanceableHubTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGlanceableHubTransitionViewModel.kt
@@ -63,6 +63,9 @@ constructor(
             )
             .onStart { emit(1f) }
 
+    // Show UMO as long as keyguard is not visible.
+    val showUmo: Flow<Boolean> = keyguardAlpha.map { alpha -> alpha == 0f }
+
     val keyguardTranslationX: Flow<StateToValue> =
         configurationInteractor
             .dimensionPixelSize(R.dimen.lockscreen_to_hub_transition_lockscreen_translation_x)
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManager.kt
index dbd71f3e3a041d4296333a28583558db37bcfa1e..a4f3e2174791f061096ce208536b6079cb731188 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManager.kt
@@ -36,7 +36,7 @@ import androidx.annotation.VisibleForTesting
 import com.android.app.animation.Interpolators
 import com.android.app.tracing.traceSection
 import com.android.keyguard.KeyguardViewController
-import com.android.systemui.communal.domain.interactor.CommunalInteractor
+import com.android.systemui.communal.ui.viewmodel.CommunalTransitionViewModel
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Main
@@ -58,7 +58,6 @@ import com.android.systemui.statusbar.policy.ConfigurationController
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import com.android.systemui.statusbar.policy.SplitShadeStateController
 import com.android.systemui.util.animation.UniqueObjectHostView
-import com.android.systemui.util.kotlin.BooleanFlowOperators.and
 import com.android.systemui.util.settings.SecureSettings
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
@@ -102,7 +101,7 @@ constructor(
     private val mediaManager: MediaDataManager,
     private val keyguardViewController: KeyguardViewController,
     private val dreamOverlayStateController: DreamOverlayStateController,
-    private val communalInteractor: CommunalInteractor,
+    communalTransitionViewModel: CommunalTransitionViewModel,
     configurationController: ConfigurationController,
     wakefulnessLifecycle: WakefulnessLifecycle,
     shadeInteractor: ShadeInteractor,
@@ -587,11 +586,10 @@ constructor(
         // Listen to the communal UI state. Make sure that communal UI is showing and hub itself is
         // available, ie. not disabled and able to be shown.
         coroutineScope.launch {
-            and(communalInteractor.isCommunalShowing, communalInteractor.isCommunalAvailable)
-                .collect { value ->
-                    isCommunalShowing = value
-                    updateDesiredLocation()
-                }
+            communalTransitionViewModel.isUmoOnCommunal.collect { value ->
+                isCommunalShowing = value
+                updateDesiredLocation(forceNoAnimation = true)
+            }
         }
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManagerTest.kt
index 45f49f01a43ea8a0af0fd31f09a39a6019d292b1..fa28036f274bdc36bbfb08bc6077c407d35e94ed 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManagerTest.kt
@@ -24,11 +24,8 @@ import android.view.ViewGroup
 import android.widget.FrameLayout
 import androidx.test.filters.SmallTest
 import com.android.keyguard.KeyguardViewController
-import com.android.systemui.Flags
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.communal.domain.interactor.communalInteractor
-import com.android.systemui.communal.domain.interactor.setCommunalAvailable
-import com.android.systemui.communal.shared.model.CommunalSceneKey
+import com.android.systemui.communal.ui.viewmodel.fakeCommunalTransitionViewModel
 import com.android.systemui.controls.controller.ControlsControllerImplTest.Companion.eq
 import com.android.systemui.dreams.DreamOverlayStateController
 import com.android.systemui.keyguard.WakefulnessLifecycle
@@ -115,10 +112,10 @@ class MediaHierarchyManagerTest : SysuiTestCase() {
     private lateinit var isQsBypassingShade: MutableStateFlow<Boolean>
     private lateinit var mediaFrame: ViewGroup
     private val configurationController = FakeConfigurationController()
-    private val communalInteractor = kosmos.communalInteractor
     private val settings = FakeSettings()
     private lateinit var testableLooper: TestableLooper
     private lateinit var fakeHandler: FakeHandler
+    private var communalTransitionViewModel = kosmos.fakeCommunalTransitionViewModel
 
     @Before
     fun setup() {
@@ -142,7 +139,7 @@ class MediaHierarchyManagerTest : SysuiTestCase() {
                 mediaDataManager,
                 keyguardViewController,
                 dreamOverlayStateController,
-                communalInteractor,
+                communalTransitionViewModel,
                 configurationController,
                 wakefulnessLifecycle,
                 shadeInteractor,
@@ -510,11 +507,7 @@ class MediaHierarchyManagerTest : SysuiTestCase() {
     @Test
     fun testCommunalLocation() =
         testScope.runTest {
-            mSetFlagsRule.enableFlags(Flags.FLAG_COMMUNAL_HUB)
-            kosmos.setCommunalAvailable(true)
-            runCurrent()
-
-            communalInteractor.onSceneChanged(CommunalSceneKey.Communal)
+            communalTransitionViewModel.setIsUmoOnCommunal(true)
             runCurrent()
             verify(mediaCarouselController)
                 .onDesiredLocationChanged(
@@ -526,7 +519,7 @@ class MediaHierarchyManagerTest : SysuiTestCase() {
                 )
             clearInvocations(mediaCarouselController)
 
-            communalInteractor.onSceneChanged(CommunalSceneKey.Blank)
+            communalTransitionViewModel.setIsUmoOnCommunal(false)
             runCurrent()
             verify(mediaCarouselController)
                 .onDesiredLocationChanged(
@@ -541,15 +534,11 @@ class MediaHierarchyManagerTest : SysuiTestCase() {
     @Test
     fun testCommunalLocation_showsOverLockscreen() =
         testScope.runTest {
-            mSetFlagsRule.enableFlags(Flags.FLAG_COMMUNAL_HUB)
-            kosmos.setCommunalAvailable(true)
-            runCurrent()
-
             // Device is on lock screen.
             whenever(statusBarStateController.state).thenReturn(StatusBarState.KEYGUARD)
 
             // UMO goes to communal even over the lock screen.
-            communalInteractor.onSceneChanged(CommunalSceneKey.Communal)
+            communalTransitionViewModel.setIsUmoOnCommunal(true)
             runCurrent()
             verify(mediaCarouselController)
                 .onDesiredLocationChanged(
@@ -564,14 +553,10 @@ class MediaHierarchyManagerTest : SysuiTestCase() {
     @Test
     fun testCommunalLocation_showsUntilQsExpands() =
         testScope.runTest {
-            mSetFlagsRule.enableFlags(Flags.FLAG_COMMUNAL_HUB)
-            kosmos.setCommunalAvailable(true)
-            runCurrent()
-
             // Device is on lock screen.
             whenever(statusBarStateController.state).thenReturn(StatusBarState.KEYGUARD)
 
-            communalInteractor.onSceneChanged(CommunalSceneKey.Communal)
+            communalTransitionViewModel.setIsUmoOnCommunal(true)
             runCurrent()
             verify(mediaCarouselController)
                 .onDesiredLocationChanged(
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModelKosmos.kt
new file mode 100644
index 0000000000000000000000000000000000000000..eaa657b76c3d5fb76c5c6de2082b37fdb4f8e65a
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModelKosmos.kt
@@ -0,0 +1,24 @@
+/*
+ * 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.communal.ui.viewmodel
+
+import com.android.systemui.kosmos.Kosmos
+
+val Kosmos.communalTransitionViewModel by
+    Kosmos.Fixture<CommunalTransitionViewModel> { fakeCommunalTransitionViewModel }
+
+val Kosmos.fakeCommunalTransitionViewModel by Kosmos.Fixture { FakeCommunalTransitionViewModel() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/ui/viewmodel/FakeCommunalTransitionViewModel.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/ui/viewmodel/FakeCommunalTransitionViewModel.kt
new file mode 100644
index 0000000000000000000000000000000000000000..409cc1d03fda316fa36ffd3e44ba833d427d93aa
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/ui/viewmodel/FakeCommunalTransitionViewModel.kt
@@ -0,0 +1,29 @@
+/*
+ * 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.communal.ui.viewmodel
+
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+
+class FakeCommunalTransitionViewModel : CommunalTransitionViewModel {
+    private val _isUmoOnCommunal = MutableStateFlow(false)
+    override val isUmoOnCommunal: Flow<Boolean> = _isUmoOnCommunal
+
+    fun setIsUmoOnCommunal(value: Boolean) {
+        _isUmoOnCommunal.value = value
+    }
+}