Merge "Refactor communal hub enablement logic" into main
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 72ae76a..f628a42 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -312,6 +312,9 @@
     <!-- Permission necessary to change car audio volume through CarAudioManager -->
     <uses-permission android:name="android.car.permission.CAR_CONTROL_AUDIO_VOLUME" />
 
+    <!-- To detect when projecting to Android Auto -->
+    <uses-permission android:name="android.permission.READ_PROJECTION_STATE" />
+
     <!-- Permission to control Android Debug Bridge (ADB) -->
     <uses-permission android:name="android.permission.MANAGE_DEBUGGING" />
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalOngoingContentStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalOngoingContentStartableTest.kt
index ed73d89..6a25069 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalOngoingContentStartableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalOngoingContentStartableTest.kt
@@ -73,12 +73,12 @@
             assertThat(fakeCommunalMediaRepository.isListening()).isFalse()
             assertThat(fakeCommunalSmartspaceRepository.isListening()).isFalse()
 
-            kosmos.setCommunalEnabled(true)
+            setCommunalEnabled(true)
 
             assertThat(fakeCommunalMediaRepository.isListening()).isTrue()
             assertThat(fakeCommunalSmartspaceRepository.isListening()).isTrue()
 
-            kosmos.setCommunalEnabled(false)
+            setCommunalEnabled(false)
 
             assertThat(fakeCommunalMediaRepository.isListening()).isFalse()
             assertThat(fakeCommunalSmartspaceRepository.isListening()).isFalse()
@@ -93,13 +93,13 @@
             assertThat(fakeCommunalMediaRepository.isListening()).isFalse()
             assertThat(fakeCommunalSmartspaceRepository.isListening()).isFalse()
 
-            kosmos.setCommunalEnabled(true)
+            setCommunalEnabled(true)
 
             // Media listening does not start when UMO is disabled.
             assertThat(fakeCommunalMediaRepository.isListening()).isFalse()
             assertThat(fakeCommunalSmartspaceRepository.isListening()).isTrue()
 
-            kosmos.setCommunalEnabled(false)
+            setCommunalEnabled(false)
 
             assertThat(fakeCommunalMediaRepository.isListening()).isFalse()
             assertThat(fakeCommunalSmartspaceRepository.isListening()).isFalse()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CarProjectionRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CarProjectionRepositoryImplTest.kt
new file mode 100644
index 0000000..f9b29e9
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CarProjectionRepositoryImplTest.kt
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2025 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.data.repository
+
+import android.app.UiModeManager
+import android.app.UiModeManager.OnProjectionStateChangedListener
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.backgroundScope
+import com.android.systemui.kosmos.collectLastValue
+import com.android.systemui.kosmos.runTest
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.useUnconfinedTestDispatcher
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.flow.launchIn
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.any
+import org.mockito.kotlin.doAnswer
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.stub
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class CarProjectionRepositoryImplTest : SysuiTestCase() {
+    private val kosmos = testKosmos().useUnconfinedTestDispatcher()
+
+    private val capturedListeners = mutableListOf<OnProjectionStateChangedListener>()
+
+    private val Kosmos.uiModeManager by
+        Kosmos.Fixture<UiModeManager> {
+            mock {
+                on {
+                    addOnProjectionStateChangedListener(
+                        eq(UiModeManager.PROJECTION_TYPE_AUTOMOTIVE),
+                        any(),
+                        any(),
+                    )
+                } doAnswer
+                    {
+                        val listener = it.getArgument<OnProjectionStateChangedListener>(2)
+                        capturedListeners.add(listener)
+                        Unit
+                    }
+
+                on { removeOnProjectionStateChangedListener(any()) } doAnswer
+                    {
+                        val listener = it.getArgument<OnProjectionStateChangedListener>(0)
+                        capturedListeners.remove(listener)
+                        Unit
+                    }
+
+                on { activeProjectionTypes } doReturn UiModeManager.PROJECTION_TYPE_NONE
+            }
+        }
+
+    private val Kosmos.underTest by
+        Kosmos.Fixture {
+            CarProjectionRepositoryImpl(
+                uiModeManager = uiModeManager,
+                bgDispatcher = testDispatcher,
+            )
+        }
+
+    @Test
+    fun testProjectionActiveUpdatesAfterCallback() =
+        kosmos.runTest {
+            val projectionActive by collectLastValue(underTest.projectionActive)
+            assertThat(projectionActive).isFalse()
+
+            setActiveProjectionType(UiModeManager.PROJECTION_TYPE_AUTOMOTIVE)
+            assertThat(projectionActive).isTrue()
+
+            setActiveProjectionType(UiModeManager.PROJECTION_TYPE_NONE)
+            assertThat(projectionActive).isFalse()
+        }
+
+    @Test
+    fun testProjectionInitialValueTrue() =
+        kosmos.runTest {
+            setActiveProjectionType(UiModeManager.PROJECTION_TYPE_AUTOMOTIVE)
+
+            val projectionActive by collectLastValue(underTest.projectionActive)
+            assertThat(projectionActive).isTrue()
+        }
+
+    @Test
+    fun testUnsubscribeWhenCancelled() =
+        kosmos.runTest {
+            val job = underTest.projectionActive.launchIn(backgroundScope)
+            assertThat(capturedListeners).hasSize(1)
+
+            job.cancel()
+            assertThat(capturedListeners).isEmpty()
+        }
+
+    private fun Kosmos.setActiveProjectionType(@UiModeManager.ProjectionType projectionType: Int) {
+        uiModeManager.stub { on { activeProjectionTypes } doReturn projectionType }
+        capturedListeners.forEach { it.onProjectionStateChanged(projectionType, emptySet()) }
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalSettingsRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalSettingsRepositoryImplTest.kt
index 5c98365..09d44a5 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalSettingsRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalSettingsRepositoryImplTest.kt
@@ -34,9 +34,7 @@
 import com.android.systemui.Flags.FLAG_GLANCEABLE_HUB_V2
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.broadcast.broadcastDispatcher
-import com.android.systemui.communal.data.model.DisabledReason
 import com.android.systemui.communal.data.repository.CommunalSettingsRepositoryImpl.Companion.GLANCEABLE_HUB_BACKGROUND_SETTING
-import com.android.systemui.communal.domain.interactor.setCommunalV2Enabled
 import com.android.systemui.communal.shared.model.CommunalBackgroundType
 import com.android.systemui.communal.shared.model.WhenToDream
 import com.android.systemui.flags.Flags.COMMUNAL_SERVICE_ENABLED
@@ -202,63 +200,6 @@
 
     @EnableFlags(FLAG_COMMUNAL_HUB)
     @Test
-    fun secondaryUserIsInvalid() =
-        kosmos.runTest {
-            val enabledState by collectLastValue(underTest.getEnabledState(SECONDARY_USER))
-
-            assertThat(enabledState?.enabled).isFalse()
-            assertThat(enabledState).containsExactly(DisabledReason.DISABLED_REASON_INVALID_USER)
-        }
-
-    @EnableFlags(FLAG_COMMUNAL_HUB, FLAG_GLANCEABLE_HUB_V2)
-    @Test
-    fun classicFlagIsDisabled() =
-        kosmos.runTest {
-            setCommunalV2Enabled(false)
-            val enabledState by collectLastValue(underTest.getEnabledState(PRIMARY_USER))
-            assertThat(enabledState?.enabled).isFalse()
-            assertThat(enabledState).containsExactly(DisabledReason.DISABLED_REASON_FLAG)
-        }
-
-    @DisableFlags(FLAG_COMMUNAL_HUB, FLAG_GLANCEABLE_HUB_V2)
-    @Test
-    fun communalHubFlagIsDisabled() =
-        kosmos.runTest {
-            val enabledState by collectLastValue(underTest.getEnabledState(PRIMARY_USER))
-            assertThat(enabledState?.enabled).isFalse()
-            assertThat(enabledState).containsExactly(DisabledReason.DISABLED_REASON_FLAG)
-        }
-
-    @EnableFlags(FLAG_COMMUNAL_HUB)
-    @Test
-    fun hubIsDisabledByUser() =
-        kosmos.runTest {
-            fakeSettings.putIntForUser(Settings.Secure.GLANCEABLE_HUB_ENABLED, 0, PRIMARY_USER.id)
-            val enabledState by collectLastValue(underTest.getEnabledState(PRIMARY_USER))
-            assertThat(enabledState?.enabled).isFalse()
-            assertThat(enabledState).containsExactly(DisabledReason.DISABLED_REASON_USER_SETTING)
-
-            fakeSettings.putIntForUser(Settings.Secure.GLANCEABLE_HUB_ENABLED, 1, SECONDARY_USER.id)
-            assertThat(enabledState?.enabled).isFalse()
-
-            fakeSettings.putIntForUser(Settings.Secure.GLANCEABLE_HUB_ENABLED, 1, PRIMARY_USER.id)
-            assertThat(enabledState?.enabled).isTrue()
-        }
-
-    @EnableFlags(FLAG_COMMUNAL_HUB)
-    @Test
-    fun hubIsDisabledByDevicePolicy() =
-        kosmos.runTest {
-            val enabledState by collectLastValue(underTest.getEnabledState(PRIMARY_USER))
-            assertThat(enabledState?.enabled).isTrue()
-
-            setKeyguardFeaturesDisabled(PRIMARY_USER, KEYGUARD_DISABLE_WIDGETS_ALL)
-            assertThat(enabledState?.enabled).isFalse()
-            assertThat(enabledState).containsExactly(DisabledReason.DISABLED_REASON_DEVICE_POLICY)
-        }
-
-    @EnableFlags(FLAG_COMMUNAL_HUB)
-    @Test
     fun widgetsAllowedForWorkProfile_isFalse_whenDisallowedByDevicePolicy() =
         kosmos.runTest {
             val widgetsAllowedForWorkProfile by
@@ -269,36 +210,6 @@
             assertThat(widgetsAllowedForWorkProfile).isFalse()
         }
 
-    @EnableFlags(FLAG_COMMUNAL_HUB)
-    @Test
-    fun hubIsEnabled_whenDisallowedByDevicePolicyForWorkProfile() =
-        kosmos.runTest {
-            val enabledStateForPrimaryUser by
-                collectLastValue(underTest.getEnabledState(PRIMARY_USER))
-            assertThat(enabledStateForPrimaryUser?.enabled).isTrue()
-
-            setKeyguardFeaturesDisabled(WORK_PROFILE, KEYGUARD_DISABLE_WIDGETS_ALL)
-            assertThat(enabledStateForPrimaryUser?.enabled).isTrue()
-        }
-
-    @EnableFlags(FLAG_COMMUNAL_HUB)
-    @Test
-    fun hubIsDisabledByUserAndDevicePolicy() =
-        kosmos.runTest {
-            val enabledState by collectLastValue(underTest.getEnabledState(PRIMARY_USER))
-            assertThat(enabledState?.enabled).isTrue()
-
-            fakeSettings.putIntForUser(Settings.Secure.GLANCEABLE_HUB_ENABLED, 0, PRIMARY_USER.id)
-            setKeyguardFeaturesDisabled(PRIMARY_USER, KEYGUARD_DISABLE_WIDGETS_ALL)
-
-            assertThat(enabledState?.enabled).isFalse()
-            assertThat(enabledState)
-                .containsExactly(
-                    DisabledReason.DISABLED_REASON_DEVICE_POLICY,
-                    DisabledReason.DISABLED_REASON_USER_SETTING,
-                )
-        }
-
     @Test
     @DisableFlags(FLAG_GLANCEABLE_HUB_BLURRED_BACKGROUND)
     fun backgroundType_defaultValue() =
@@ -327,26 +238,6 @@
         }
 
     @Test
-    fun screensaverDisabledByUser() =
-        kosmos.runTest {
-            val enabledState by collectLastValue(underTest.getScreensaverEnabledState(PRIMARY_USER))
-
-            fakeSettings.putIntForUser(Settings.Secure.SCREENSAVER_ENABLED, 0, PRIMARY_USER.id)
-
-            assertThat(enabledState).isFalse()
-        }
-
-    @Test
-    fun screensaverEnabledByUser() =
-        kosmos.runTest {
-            val enabledState by collectLastValue(underTest.getScreensaverEnabledState(PRIMARY_USER))
-
-            fakeSettings.putIntForUser(Settings.Secure.SCREENSAVER_ENABLED, 1, PRIMARY_USER.id)
-
-            assertThat(enabledState).isTrue()
-        }
-
-    @Test
     fun whenToDream_charging() =
         kosmos.runTest {
             val whenToDreamState by collectLastValue(underTest.getWhenToDreamState(PRIMARY_USER))
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CarProjectionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CarProjectionInteractorTest.kt
new file mode 100644
index 0000000..fc4cd43
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CarProjectionInteractorTest.kt
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2025 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.domain.interactor
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.communal.data.repository.carProjectionRepository
+import com.android.systemui.communal.data.repository.fake
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.collectLastValue
+import com.android.systemui.kosmos.runTest
+import com.android.systemui.kosmos.useUnconfinedTestDispatcher
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class CarProjectionInteractorTest : SysuiTestCase() {
+    private val kosmos = testKosmos().useUnconfinedTestDispatcher()
+
+    private val Kosmos.underTest by Kosmos.Fixture { carProjectionInteractor }
+
+    @Test
+    fun testProjectionActive() =
+        kosmos.runTest {
+            val projectionActive by collectLastValue(underTest.projectionActive)
+            assertThat(projectionActive).isFalse()
+
+            carProjectionRepository.fake.setProjectionActive(true)
+            assertThat(projectionActive).isTrue()
+        }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalAutoOpenInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalAutoOpenInteractorTest.kt
new file mode 100644
index 0000000..f4a1c90
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalAutoOpenInteractorTest.kt
@@ -0,0 +1,173 @@
+/*
+ * Copyright (C) 2025 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.domain.interactor
+
+import android.provider.Settings
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.common.data.repository.batteryRepository
+import com.android.systemui.common.data.repository.fake
+import com.android.systemui.communal.data.model.FEATURE_AUTO_OPEN
+import com.android.systemui.communal.data.model.FEATURE_MANUAL_OPEN
+import com.android.systemui.communal.data.model.SuppressionReason
+import com.android.systemui.communal.posturing.data.repository.fake
+import com.android.systemui.communal.posturing.data.repository.posturingRepository
+import com.android.systemui.communal.posturing.shared.model.PosturedState
+import com.android.systemui.dock.DockManager
+import com.android.systemui.dock.fakeDockManager
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.collectLastValue
+import com.android.systemui.kosmos.runTest
+import com.android.systemui.kosmos.useUnconfinedTestDispatcher
+import com.android.systemui.testKosmos
+import com.android.systemui.user.data.repository.FakeUserRepository.Companion.MAIN_USER_ID
+import com.android.systemui.user.data.repository.fakeUserRepository
+import com.android.systemui.util.settings.fakeSettings
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.runBlocking
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class CommunalAutoOpenInteractorTest : SysuiTestCase() {
+    private val kosmos = testKosmos().useUnconfinedTestDispatcher()
+
+    private val Kosmos.underTest by Kosmos.Fixture { communalAutoOpenInteractor }
+
+    @Before
+    fun setUp() {
+        runBlocking { kosmos.fakeUserRepository.asMainUser() }
+        with(kosmos.fakeSettings) {
+            putBoolForUser(Settings.Secure.SCREENSAVER_ACTIVATE_ON_SLEEP, false, MAIN_USER_ID)
+            putBoolForUser(Settings.Secure.SCREENSAVER_ACTIVATE_ON_DOCK, false, MAIN_USER_ID)
+            putBoolForUser(Settings.Secure.SCREENSAVER_ACTIVATE_ON_POSTURED, false, MAIN_USER_ID)
+        }
+    }
+
+    @Test
+    fun testStartWhileCharging() =
+        kosmos.runTest {
+            val shouldAutoOpen by collectLastValue(underTest.shouldAutoOpen)
+            val suppressionReason by collectLastValue(underTest.suppressionReason)
+
+            fakeSettings.putBoolForUser(
+                Settings.Secure.SCREENSAVER_ACTIVATE_ON_SLEEP,
+                true,
+                MAIN_USER_ID,
+            )
+
+            batteryRepository.fake.setDevicePluggedIn(false)
+            assertThat(shouldAutoOpen).isFalse()
+            assertThat(suppressionReason)
+                .isEqualTo(
+                    SuppressionReason.ReasonWhenToAutoShow(FEATURE_AUTO_OPEN or FEATURE_MANUAL_OPEN)
+                )
+
+            batteryRepository.fake.setDevicePluggedIn(true)
+            assertThat(shouldAutoOpen).isTrue()
+            assertThat(suppressionReason).isNull()
+        }
+
+    @Test
+    fun testStartWhileDocked() =
+        kosmos.runTest {
+            val shouldAutoOpen by collectLastValue(underTest.shouldAutoOpen)
+            val suppressionReason by collectLastValue(underTest.suppressionReason)
+
+            fakeSettings.putBoolForUser(
+                Settings.Secure.SCREENSAVER_ACTIVATE_ON_DOCK,
+                true,
+                MAIN_USER_ID,
+            )
+
+            batteryRepository.fake.setDevicePluggedIn(true)
+            fakeDockManager.setIsDocked(false)
+
+            assertThat(shouldAutoOpen).isFalse()
+            assertThat(suppressionReason)
+                .isEqualTo(
+                    SuppressionReason.ReasonWhenToAutoShow(FEATURE_AUTO_OPEN or FEATURE_MANUAL_OPEN)
+                )
+
+            fakeDockManager.setIsDocked(true)
+            fakeDockManager.setDockEvent(DockManager.STATE_DOCKED)
+            assertThat(shouldAutoOpen).isTrue()
+            assertThat(suppressionReason).isNull()
+        }
+
+    @Test
+    fun testStartWhilePostured() =
+        kosmos.runTest {
+            val shouldAutoOpen by collectLastValue(underTest.shouldAutoOpen)
+            val suppressionReason by collectLastValue(underTest.suppressionReason)
+
+            fakeSettings.putBoolForUser(
+                Settings.Secure.SCREENSAVER_ACTIVATE_ON_POSTURED,
+                true,
+                MAIN_USER_ID,
+            )
+
+            batteryRepository.fake.setDevicePluggedIn(true)
+            posturingRepository.fake.setPosturedState(PosturedState.NotPostured)
+
+            assertThat(shouldAutoOpen).isFalse()
+            assertThat(suppressionReason)
+                .isEqualTo(
+                    SuppressionReason.ReasonWhenToAutoShow(FEATURE_AUTO_OPEN or FEATURE_MANUAL_OPEN)
+                )
+
+            posturingRepository.fake.setPosturedState(PosturedState.Postured(1f))
+            assertThat(shouldAutoOpen).isTrue()
+            assertThat(suppressionReason).isNull()
+        }
+
+    @Test
+    fun testStartNever() =
+        kosmos.runTest {
+            val shouldAutoOpen by collectLastValue(underTest.shouldAutoOpen)
+            val suppressionReason by collectLastValue(underTest.suppressionReason)
+
+            fakeSettings.putBoolForUser(
+                Settings.Secure.SCREENSAVER_ACTIVATE_ON_SLEEP,
+                false,
+                MAIN_USER_ID,
+            )
+            fakeSettings.putBoolForUser(
+                Settings.Secure.SCREENSAVER_ACTIVATE_ON_DOCK,
+                false,
+                MAIN_USER_ID,
+            )
+            fakeSettings.putBoolForUser(
+                Settings.Secure.SCREENSAVER_ACTIVATE_ON_POSTURED,
+                false,
+                MAIN_USER_ID,
+            )
+
+            batteryRepository.fake.setDevicePluggedIn(true)
+            posturingRepository.fake.setPosturedState(PosturedState.Postured(1f))
+            fakeDockManager.setIsDocked(true)
+
+            assertThat(shouldAutoOpen).isFalse()
+            assertThat(suppressionReason)
+                .isEqualTo(
+                    SuppressionReason.ReasonWhenToAutoShow(FEATURE_AUTO_OPEN or FEATURE_MANUAL_OPEN)
+                )
+        }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorCommunalDisabledTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorCommunalDisabledTest.kt
deleted file mode 100644
index beec184..0000000
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorCommunalDisabledTest.kt
+++ /dev/null
@@ -1,82 +0,0 @@
-/*
- * 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.domain.interactor
-
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.SmallTest
-import com.android.systemui.Flags.FLAG_COMMUNAL_HUB
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.communal.data.repository.FakeCommunalSceneRepository
-import com.android.systemui.communal.data.repository.FakeCommunalWidgetRepository
-import com.android.systemui.communal.data.repository.fakeCommunalSceneRepository
-import com.android.systemui.communal.data.repository.fakeCommunalWidgetRepository
-import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
-import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
-import com.android.systemui.kosmos.testScope
-import com.android.systemui.testKosmos
-import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.test.runCurrent
-import kotlinx.coroutines.test.runTest
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-
-/**
- * This class is a variation of the [CommunalInteractorTest] for cases where communal is disabled.
- */
-@SmallTest
-@RunWith(AndroidJUnit4::class)
-class CommunalInteractorCommunalDisabledTest : SysuiTestCase() {
-    private val kosmos = testKosmos()
-    private val testScope = kosmos.testScope
-
-    private lateinit var communalRepository: FakeCommunalSceneRepository
-    private lateinit var widgetRepository: FakeCommunalWidgetRepository
-    private lateinit var keyguardRepository: FakeKeyguardRepository
-
-    private lateinit var underTest: CommunalInteractor
-
-    @Before
-    fun setUp() {
-        communalRepository = kosmos.fakeCommunalSceneRepository
-        widgetRepository = kosmos.fakeCommunalWidgetRepository
-        keyguardRepository = kosmos.fakeKeyguardRepository
-
-        mSetFlagsRule.disableFlags(FLAG_COMMUNAL_HUB)
-
-        underTest = kosmos.communalInteractor
-    }
-
-    @Test
-    fun isCommunalEnabled_false() =
-        testScope.runTest { assertThat(underTest.isCommunalEnabled.value).isFalse() }
-
-    @Test
-    fun isCommunalAvailable_whenStorageUnlock_false() =
-        testScope.runTest {
-            val isCommunalAvailable by collectLastValue(underTest.isCommunalAvailable)
-
-            assertThat(isCommunalAvailable).isFalse()
-
-            keyguardRepository.setIsEncryptedOrLockdown(false)
-            runCurrent()
-
-            assertThat(isCommunalAvailable).isFalse()
-        }
-}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
index 8424746..b65ecf46 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
@@ -21,7 +21,6 @@
 import android.app.admin.devicePolicyManager
 import android.content.Intent
 import android.content.pm.UserInfo
-import android.content.res.mainResources
 import android.os.UserHandle
 import android.os.UserManager
 import android.os.userManager
@@ -39,9 +38,8 @@
 import com.android.systemui.Flags.FLAG_GLANCEABLE_HUB_V2
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.broadcast.broadcastDispatcher
-import com.android.systemui.common.data.repository.batteryRepository
-import com.android.systemui.common.data.repository.fake
 import com.android.systemui.communal.data.model.CommunalSmartspaceTimer
+import com.android.systemui.communal.data.model.SuppressionReason
 import com.android.systemui.communal.data.repository.fakeCommunalMediaRepository
 import com.android.systemui.communal.data.repository.fakeCommunalPrefsRepository
 import com.android.systemui.communal.data.repository.fakeCommunalSceneRepository
@@ -50,14 +48,9 @@
 import com.android.systemui.communal.data.repository.fakeCommunalWidgetRepository
 import com.android.systemui.communal.domain.model.CommunalContentModel
 import com.android.systemui.communal.domain.model.CommunalTransitionProgressModel
-import com.android.systemui.communal.posturing.data.repository.fake
-import com.android.systemui.communal.posturing.data.repository.posturingRepository
-import com.android.systemui.communal.posturing.shared.model.PosturedState
 import com.android.systemui.communal.shared.model.CommunalContentSize
 import com.android.systemui.communal.shared.model.CommunalScenes
 import com.android.systemui.communal.shared.model.EditModeState
-import com.android.systemui.dock.DockManager
-import com.android.systemui.dock.fakeDockManager
 import com.android.systemui.flags.EnableSceneContainer
 import com.android.systemui.flags.Flags
 import com.android.systemui.flags.fakeFeatureFlagsClassic
@@ -75,19 +68,16 @@
 import com.android.systemui.settings.fakeUserTracker
 import com.android.systemui.statusbar.phone.fakeManagedProfileController
 import com.android.systemui.testKosmos
-import com.android.systemui.user.data.repository.FakeUserRepository
 import com.android.systemui.user.data.repository.fakeUserRepository
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.argumentCaptor
 import com.android.systemui.util.mockito.capture
 import com.android.systemui.util.mockito.nullable
 import com.android.systemui.util.mockito.whenever
-import com.android.systemui.util.settings.fakeSettings
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.test.advanceTimeBy
-import org.junit.After
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -98,10 +88,6 @@
 import platform.test.runner.parameterized.ParameterizedAndroidJunit4
 import platform.test.runner.parameterized.Parameters
 
-/**
- * This class of test cases assume that communal is enabled. For disabled cases, see
- * [CommunalInteractorCommunalDisabledTest].
- */
 @SmallTest
 @RunWith(ParameterizedAndroidJunit4::class)
 class CommunalInteractorTest(flags: FlagsParameterization) : SysuiTestCase() {
@@ -109,10 +95,7 @@
         UserInfo(/* id= */ 0, /* name= */ "primary user", /* flags= */ UserInfo.FLAG_MAIN)
     private val secondaryUser = UserInfo(/* id= */ 1, /* name= */ "secondary user", /* flags= */ 0)
 
-    private val kosmos =
-        testKosmos()
-            .apply { mainResources = mContext.orCreateTestableResources.resources }
-            .useUnconfinedTestDispatcher()
+    private val kosmos = testKosmos().useUnconfinedTestDispatcher()
 
     private val Kosmos.underTest by Kosmos.Fixture { communalInteractor }
 
@@ -128,104 +111,40 @@
 
         kosmos.fakeFeatureFlagsClassic.set(Flags.COMMUNAL_SERVICE_ENABLED, true)
         mSetFlagsRule.enableFlags(FLAG_COMMUNAL_HUB)
-
-        mContext.orCreateTestableResources.addOverride(
-            com.android.internal.R.bool.config_dreamsActivatedOnSleepByDefault,
-            false,
-        )
-        mContext.orCreateTestableResources.addOverride(
-            com.android.internal.R.bool.config_dreamsActivatedOnDockByDefault,
-            false,
-        )
-        mContext.orCreateTestableResources.addOverride(
-            com.android.internal.R.bool.config_dreamsActivatedOnPosturedByDefault,
-            false,
-        )
-    }
-
-    @After
-    fun tearDown() {
-        mContext.orCreateTestableResources.removeOverride(
-            com.android.internal.R.bool.config_dreamsActivatedOnSleepByDefault
-        )
-        mContext.orCreateTestableResources.removeOverride(
-            com.android.internal.R.bool.config_dreamsActivatedOnDockByDefault
-        )
-        mContext.orCreateTestableResources.removeOverride(
-            com.android.internal.R.bool.config_dreamsActivatedOnPosturedByDefault
-        )
     }
 
     @Test
     fun communalEnabled_true() =
         kosmos.runTest {
-            fakeUserRepository.setSelectedUserInfo(mainUser)
+            communalSettingsInteractor.setSuppressionReasons(emptyList())
             assertThat(underTest.isCommunalEnabled.value).isTrue()
         }
 
     @Test
-    fun isCommunalAvailable_mainUserUnlockedAndMainUser_true() =
-        kosmos.runTest {
-            val isAvailable by collectLastValue(underTest.isCommunalAvailable)
-            assertThat(isAvailable).isFalse()
-
-            fakeUserRepository.setUserUnlocked(FakeUserRepository.MAIN_USER_ID, true)
-            fakeUserRepository.setSelectedUserInfo(mainUser)
-            fakeKeyguardRepository.setKeyguardShowing(true)
-
-            assertThat(isAvailable).isTrue()
-        }
-
-    @Test
-    fun isCommunalAvailable_mainUserLockedAndMainUser_false() =
-        kosmos.runTest {
-            val isAvailable by collectLastValue(underTest.isCommunalAvailable)
-            assertThat(isAvailable).isFalse()
-
-            fakeUserRepository.setUserUnlocked(FakeUserRepository.MAIN_USER_ID, false)
-            fakeUserRepository.setSelectedUserInfo(mainUser)
-            fakeKeyguardRepository.setKeyguardShowing(true)
-
-            assertThat(isAvailable).isFalse()
-        }
-
-    @Test
-    fun isCommunalAvailable_mainUserUnlockedAndSecondaryUser_false() =
-        kosmos.runTest {
-            val isAvailable by collectLastValue(underTest.isCommunalAvailable)
-            assertThat(isAvailable).isFalse()
-
-            fakeUserRepository.setUserUnlocked(FakeUserRepository.MAIN_USER_ID, true)
-            fakeUserRepository.setSelectedUserInfo(secondaryUser)
-            fakeKeyguardRepository.setKeyguardShowing(true)
-
-            assertThat(isAvailable).isFalse()
-        }
-
-    @Test
     fun isCommunalAvailable_whenKeyguardShowing_true() =
         kosmos.runTest {
+            communalSettingsInteractor.setSuppressionReasons(emptyList())
+            fakeKeyguardRepository.setKeyguardShowing(false)
+
             val isAvailable by collectLastValue(underTest.isCommunalAvailable)
             assertThat(isAvailable).isFalse()
 
-            fakeUserRepository.setUserUnlocked(FakeUserRepository.MAIN_USER_ID, true)
-            fakeUserRepository.setSelectedUserInfo(mainUser)
             fakeKeyguardRepository.setKeyguardShowing(true)
-
             assertThat(isAvailable).isTrue()
         }
 
     @Test
-    fun isCommunalAvailable_communalDisabled_false() =
+    fun isCommunalAvailable_suppressed() =
         kosmos.runTest {
-            mSetFlagsRule.disableFlags(FLAG_COMMUNAL_HUB, FLAG_GLANCEABLE_HUB_V2)
+            communalSettingsInteractor.setSuppressionReasons(emptyList())
+            fakeKeyguardRepository.setKeyguardShowing(true)
 
             val isAvailable by collectLastValue(underTest.isCommunalAvailable)
-            assertThat(isAvailable).isFalse()
+            assertThat(isAvailable).isTrue()
 
-            fakeUserRepository.setUserUnlocked(FakeUserRepository.MAIN_USER_ID, false)
-            fakeUserRepository.setSelectedUserInfo(mainUser)
-            fakeKeyguardRepository.setKeyguardShowing(true)
+            communalSettingsInteractor.setSuppressionReasons(
+                listOf(SuppressionReason.ReasonUnknown())
+            )
 
             assertThat(isAvailable).isFalse()
         }
@@ -1280,66 +1199,6 @@
                 .inOrder()
         }
 
-    @Test
-    fun showCommunalWhileCharging() =
-        kosmos.runTest {
-            fakeUserRepository.setUserUnlocked(FakeUserRepository.MAIN_USER_ID, true)
-            fakeUserRepository.setSelectedUserInfo(mainUser)
-            fakeKeyguardRepository.setKeyguardShowing(true)
-            fakeSettings.putIntForUser(
-                Settings.Secure.SCREENSAVER_ACTIVATE_ON_SLEEP,
-                1,
-                mainUser.id,
-            )
-
-            val shouldShowCommunal by collectLastValue(underTest.shouldShowCommunal)
-            batteryRepository.fake.setDevicePluggedIn(false)
-            assertThat(shouldShowCommunal).isFalse()
-
-            batteryRepository.fake.setDevicePluggedIn(true)
-            assertThat(shouldShowCommunal).isTrue()
-        }
-
-    @Test
-    fun showCommunalWhilePosturedAndCharging() =
-        kosmos.runTest {
-            fakeUserRepository.setUserUnlocked(FakeUserRepository.MAIN_USER_ID, true)
-            fakeUserRepository.setSelectedUserInfo(mainUser)
-            fakeKeyguardRepository.setKeyguardShowing(true)
-            fakeSettings.putIntForUser(
-                Settings.Secure.SCREENSAVER_ACTIVATE_ON_POSTURED,
-                1,
-                mainUser.id,
-            )
-
-            val shouldShowCommunal by collectLastValue(underTest.shouldShowCommunal)
-            batteryRepository.fake.setDevicePluggedIn(true)
-            posturingRepository.fake.setPosturedState(PosturedState.NotPostured)
-            assertThat(shouldShowCommunal).isFalse()
-
-            posturingRepository.fake.setPosturedState(PosturedState.Postured(1f))
-            assertThat(shouldShowCommunal).isTrue()
-        }
-
-    @Test
-    fun showCommunalWhileDocked() =
-        kosmos.runTest {
-            fakeUserRepository.setUserUnlocked(FakeUserRepository.MAIN_USER_ID, true)
-            fakeUserRepository.setSelectedUserInfo(mainUser)
-            fakeKeyguardRepository.setKeyguardShowing(true)
-            fakeSettings.putIntForUser(Settings.Secure.SCREENSAVER_ACTIVATE_ON_DOCK, 1, mainUser.id)
-
-            batteryRepository.fake.setDevicePluggedIn(true)
-            fakeDockManager.setIsDocked(false)
-
-            val shouldShowCommunal by collectLastValue(underTest.shouldShowCommunal)
-            assertThat(shouldShowCommunal).isFalse()
-
-            fakeDockManager.setIsDocked(true)
-            fakeDockManager.setDockEvent(DockManager.STATE_DOCKED)
-            assertThat(shouldShowCommunal).isTrue()
-        }
-
     private fun setKeyguardFeaturesDisabled(user: UserInfo, disabledFlags: Int) {
         whenever(kosmos.devicePolicyManager.getKeyguardDisabledFeatures(nullable(), eq(user.id)))
             .thenReturn(disabledFlags)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
index 8dc7a33..b8dbc9f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
@@ -32,9 +32,9 @@
 import com.android.systemui.Flags.FLAG_GLANCEABLE_HUB_V2
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.bouncer.data.repository.fakeKeyguardBouncerRepository
-import com.android.systemui.common.data.repository.batteryRepository
-import com.android.systemui.common.data.repository.fake
 import com.android.systemui.communal.data.model.CommunalSmartspaceTimer
+import com.android.systemui.communal.data.model.FEATURE_MANUAL_OPEN
+import com.android.systemui.communal.data.model.SuppressionReason
 import com.android.systemui.communal.data.repository.FakeCommunalMediaRepository
 import com.android.systemui.communal.data.repository.FakeCommunalSceneRepository
 import com.android.systemui.communal.data.repository.FakeCommunalSmartspaceRepository
@@ -50,6 +50,7 @@
 import com.android.systemui.communal.domain.interactor.communalSceneInteractor
 import com.android.systemui.communal.domain.interactor.communalSettingsInteractor
 import com.android.systemui.communal.domain.interactor.communalTutorialInteractor
+import com.android.systemui.communal.domain.interactor.setCommunalEnabled
 import com.android.systemui.communal.domain.interactor.setCommunalV2ConfigEnabled
 import com.android.systemui.communal.domain.model.CommunalContentModel
 import com.android.systemui.communal.shared.log.CommunalMetricsLogger
@@ -101,7 +102,6 @@
 import com.android.systemui.testKosmos
 import com.android.systemui.user.data.repository.FakeUserRepository
 import com.android.systemui.user.data.repository.fakeUserRepository
-import com.android.systemui.util.settings.fakeSettings
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.test.advanceTimeBy
@@ -128,7 +128,9 @@
 @RunWith(ParameterizedAndroidJunit4::class)
 class CommunalViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
     @Mock private lateinit var mediaHost: MediaHost
+
     @Mock private lateinit var mediaCarouselScrollHandler: MediaCarouselScrollHandler
+
     @Mock private lateinit var metricsLogger: CommunalMetricsLogger
 
     private val kosmos = testKosmos()
@@ -212,11 +214,8 @@
     @Test
     fun tutorial_tutorialNotCompletedAndKeyguardVisible_showTutorialContent() =
         testScope.runTest {
-            // Keyguard showing, storage unlocked, main user, and tutorial not started.
             keyguardRepository.setKeyguardShowing(true)
-            keyguardRepository.setKeyguardOccluded(false)
-            userRepository.setUserUnlocked(FakeUserRepository.MAIN_USER_ID, true)
-            setIsMainUser(true)
+            kosmos.setCommunalEnabled(true)
             tutorialRepository.setTutorialSettingState(
                 Settings.Secure.HUB_MODE_TUTORIAL_NOT_STARTED
             )
@@ -951,21 +950,16 @@
     fun swipeToCommunal() =
         kosmos.runTest {
             setCommunalV2ConfigEnabled(true)
-            val mainUser = fakeUserRepository.asMainUser()
-            fakeKeyguardRepository.setKeyguardShowing(true)
-            fakeUserRepository.setUserUnlocked(mainUser.id, true)
-            fakeUserTracker.set(userInfos = listOf(mainUser), selectedUserIndex = 0)
-            fakeSettings.putIntForUser(
-                Settings.Secure.SCREENSAVER_ACTIVATE_ON_SLEEP,
-                1,
-                mainUser.id,
+            // Suppress manual opening
+            communalSettingsInteractor.setSuppressionReasons(
+                listOf(SuppressionReason.ReasonUnknown(FEATURE_MANUAL_OPEN))
             )
 
             val viewModel = createViewModel()
             val swipeToHubEnabled by collectLastValue(viewModel.swipeToHubEnabled)
             assertThat(swipeToHubEnabled).isFalse()
 
-            batteryRepository.fake.setDevicePluggedIn(true)
+            communalSettingsInteractor.setSuppressionReasons(emptyList())
             assertThat(swipeToHubEnabled).isTrue()
 
             keyguardTransitionRepository.sendTransitionStep(
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostStartableTest.kt
index c15f797..df10d05 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostStartableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostStartableTest.kt
@@ -17,7 +17,6 @@
 package com.android.systemui.communal.widgets
 
 import android.content.pm.UserInfo
-import android.provider.Settings
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.Flags.FLAG_COMMUNAL_HUB
@@ -25,6 +24,7 @@
 import com.android.systemui.communal.data.repository.fakeCommunalWidgetRepository
 import com.android.systemui.communal.domain.interactor.communalInteractor
 import com.android.systemui.communal.domain.interactor.communalSettingsInteractor
+import com.android.systemui.communal.domain.interactor.setCommunalEnabled
 import com.android.systemui.communal.shared.model.FakeGlanceableHubMultiUserHelper
 import com.android.systemui.communal.shared.model.fakeGlanceableHubMultiUserHelper
 import com.android.systemui.coroutines.collectLastValue
@@ -37,11 +37,9 @@
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.settings.fakeUserTracker
 import com.android.systemui.testKosmos
-import com.android.systemui.user.data.repository.FakeUserRepository.Companion.MAIN_USER_ID
 import com.android.systemui.user.data.repository.fakeUserRepository
 import com.android.systemui.user.domain.interactor.userLockedInteractor
 import com.android.systemui.util.mockito.whenever
-import com.android.systemui.util.settings.fakeSettings
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.flow.MutableSharedFlow
 import kotlinx.coroutines.test.runCurrent
@@ -282,22 +280,12 @@
             }
         }
 
-    private suspend fun setCommunalAvailable(
-        available: Boolean,
-        setKeyguardShowing: Boolean = true,
-    ) =
+    private fun setCommunalAvailable(available: Boolean, setKeyguardShowing: Boolean = true) =
         with(kosmos) {
-            fakeUserRepository.setUserUnlocked(MAIN_USER_ID, true)
-            fakeUserRepository.setSelectedUserInfo(MAIN_USER_INFO)
+            setCommunalEnabled(available)
             if (setKeyguardShowing) {
                 fakeKeyguardRepository.setKeyguardShowing(true)
             }
-            val settingsValue = if (available) 1 else 0
-            fakeSettings.putIntForUser(
-                Settings.Secure.GLANCEABLE_HUB_ENABLED,
-                settingsValue,
-                MAIN_USER_INFO.id,
-            )
         }
 
     private companion object {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorTest.kt
index 6c4325a..046d92d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorTest.kt
@@ -35,7 +35,6 @@
 import android.os.PowerManager
 import android.platform.test.annotations.DisableFlags
 import android.platform.test.annotations.EnableFlags
-import android.provider.Settings
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.Flags
@@ -43,8 +42,6 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository
 import com.android.systemui.bouncer.data.repository.fakeKeyguardBouncerRepository
-import com.android.systemui.common.data.repository.batteryRepository
-import com.android.systemui.common.data.repository.fake
 import com.android.systemui.communal.data.repository.fakeCommunalSceneRepository
 import com.android.systemui.communal.domain.interactor.communalSceneInteractor
 import com.android.systemui.communal.domain.interactor.setCommunalV2Available
@@ -72,7 +69,6 @@
 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.fakeSettings
 import com.google.common.truth.Truth
 import junit.framework.Assert.assertEquals
 import kotlinx.coroutines.runBlocking
@@ -433,9 +429,7 @@
     @EnableFlags(FLAG_GLANCEABLE_HUB_V2)
     fun testTransitionToGlanceableHub_onWakeUpFromAod() =
         kosmos.runTest {
-            val user = setCommunalV2Available(true)
-            fakeSettings.putIntForUser(Settings.Secure.SCREENSAVER_ACTIVATE_ON_SLEEP, 1, user.id)
-            batteryRepository.fake.setDevicePluggedIn(true)
+            setCommunalV2Available(true)
 
             val currentScene by collectLastValue(communalSceneInteractor.currentScene)
             fakeCommunalSceneRepository.changeScene(CommunalScenes.Blank)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorTest.kt
index 9be786f..096c3da 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorTest.kt
@@ -193,6 +193,7 @@
 
     @Test
     @EnableFlags(FLAG_KEYGUARD_WM_STATE_REFACTOR)
+    @DisableFlags(FLAG_GLANCEABLE_HUB_V2)
     fun testTransitionToLockscreen_onWake_canNotDream_glanceableHubAvailable() =
         kosmos.runTest {
             whenever(dreamManager.canStartDreaming(anyBoolean())).thenReturn(false)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
index 8df70ef..7d5e9a5 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
@@ -33,7 +33,7 @@
 import com.android.systemui.communal.domain.interactor.CommunalSceneTransitionInteractor
 import com.android.systemui.communal.domain.interactor.communalSceneInteractor
 import com.android.systemui.communal.domain.interactor.communalSceneTransitionInteractor
-import com.android.systemui.communal.domain.interactor.setCommunalAvailable
+import com.android.systemui.communal.domain.interactor.setCommunalV2Available
 import com.android.systemui.communal.domain.interactor.setCommunalV2ConfigEnabled
 import com.android.systemui.communal.domain.interactor.setCommunalV2Enabled
 import com.android.systemui.communal.shared.model.CommunalScenes
@@ -1004,16 +1004,15 @@
     @BrokenWithSceneContainer(339465026)
     fun occludedToGlanceableHub_communalKtfRefactor() =
         testScope.runTest {
-            // GIVEN a device on lockscreen and communal is available
-            keyguardRepository.setKeyguardShowing(true)
-            kosmos.setCommunalAvailable(true)
-            runCurrent()
-
             // GIVEN a prior transition has run to OCCLUDED from GLANCEABLE_HUB
             runTransitionAndSetWakefulness(KeyguardState.GLANCEABLE_HUB, KeyguardState.OCCLUDED)
             keyguardRepository.setKeyguardOccluded(true)
             runCurrent()
 
+            // GIVEN a device on lockscreen and communal is available
+            kosmos.setCommunalV2Available(true)
+            runCurrent()
+
             // WHEN occlusion ends
             keyguardRepository.setKeyguardOccluded(false)
             runCurrent()
diff --git a/packages/SystemUI/src/com/android/systemui/communal/CommunalSuppressionStartable.kt b/packages/SystemUI/src/com/android/systemui/communal/CommunalSuppressionStartable.kt
new file mode 100644
index 0000000..6a611ec
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/CommunalSuppressionStartable.kt
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2025 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
+
+import com.android.systemui.CoreStartable
+import com.android.systemui.communal.data.model.SuppressionReason
+import com.android.systemui.communal.domain.interactor.CommunalSettingsInteractor
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.log.dagger.CommunalTableLog
+import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.log.table.logDiffsForTable
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+
+@SysUISingleton
+class CommunalSuppressionStartable
+@Inject
+constructor(
+    @Application private val applicationScope: CoroutineScope,
+    @Background private val bgDispatcher: CoroutineDispatcher,
+    private val suppressionFlows: Set<@JvmSuppressWildcards Flow<SuppressionReason?>>,
+    private val communalSettingsInteractor: CommunalSettingsInteractor,
+    @CommunalTableLog private val tableLogBuffer: TableLogBuffer,
+) : CoreStartable {
+    override fun start() {
+        getSuppressionReasons()
+            .onEach { reasons -> communalSettingsInteractor.setSuppressionReasons(reasons) }
+            .logDiffsForTable(
+                tableLogBuffer = tableLogBuffer,
+                columnName = "suppressionReasons",
+                initialValue = emptyList(),
+            )
+            .flowOn(bgDispatcher)
+            .launchIn(applicationScope)
+    }
+
+    private fun getSuppressionReasons(): Flow<List<SuppressionReason>> {
+        if (!communalSettingsInteractor.isCommunalFlagEnabled()) {
+            return flowOf(listOf(SuppressionReason.ReasonFlagDisabled))
+        }
+        return combine(suppressionFlows) { reasons -> reasons.filterNotNull() }
+    }
+}
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 bb3be53..a31c0bd 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt
@@ -29,6 +29,7 @@
 import com.android.systemui.communal.data.repository.CommunalTutorialRepositoryModule
 import com.android.systemui.communal.data.repository.CommunalWidgetRepositoryModule
 import com.android.systemui.communal.domain.interactor.CommunalSceneTransitionInteractor
+import com.android.systemui.communal.domain.suppression.dagger.CommunalSuppressionModule
 import com.android.systemui.communal.shared.log.CommunalMetricsLogger
 import com.android.systemui.communal.shared.log.CommunalStatsLogProxyImpl
 import com.android.systemui.communal.shared.model.CommunalScenes
@@ -70,6 +71,7 @@
             CommunalSmartspaceRepositoryModule::class,
             CommunalStartableModule::class,
             GlanceableHubWidgetManagerModule::class,
+            CommunalSuppressionModule::class,
         ]
 )
 interface CommunalModule {
diff --git a/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalStartableModule.kt b/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalStartableModule.kt
index 7358aa7..a4f75e8 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalStartableModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalStartableModule.kt
@@ -22,6 +22,7 @@
 import com.android.systemui.communal.CommunalMetricsStartable
 import com.android.systemui.communal.CommunalOngoingContentStartable
 import com.android.systemui.communal.CommunalSceneStartable
+import com.android.systemui.communal.CommunalSuppressionStartable
 import com.android.systemui.communal.DevicePosturingListener
 import com.android.systemui.communal.log.CommunalLoggerStartable
 import com.android.systemui.communal.widgets.CommunalAppWidgetHostStartable
@@ -73,4 +74,9 @@
     @IntoMap
     @ClassKey(DevicePosturingListener::class)
     fun bindDevicePosturingistener(impl: DevicePosturingListener): CoreStartable
+
+    @Binds
+    @IntoMap
+    @ClassKey(CommunalSuppressionStartable::class)
+    fun bindCommunalSuppressionStartable(impl: CommunalSuppressionStartable): CoreStartable
 }
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/model/CommunalEnabledState.kt b/packages/SystemUI/src/com/android/systemui/communal/data/model/CommunalEnabledState.kt
deleted file mode 100644
index 83a5bdb..0000000
--- a/packages/SystemUI/src/com/android/systemui/communal/data/model/CommunalEnabledState.kt
+++ /dev/null
@@ -1,69 +0,0 @@
-/*
- * 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.data.model
-
-import com.android.systemui.log.table.Diffable
-import com.android.systemui.log.table.TableRowLogger
-import java.util.EnumSet
-
-/** Reasons that communal is disabled, primarily for logging. */
-enum class DisabledReason(val loggingString: String) {
-    /** Communal should be disabled due to invalid current user */
-    DISABLED_REASON_INVALID_USER("invalidUser"),
-    /** Communal should be disabled due to the flag being off */
-    DISABLED_REASON_FLAG("flag"),
-    /** Communal should be disabled because the user has turned off the setting */
-    DISABLED_REASON_USER_SETTING("userSetting"),
-    /** Communal is disabled by the device policy app */
-    DISABLED_REASON_DEVICE_POLICY("devicePolicy"),
-}
-
-/**
- * Model representing the reasons communal hub should be disabled. Allows logging reasons separately
- * for debugging.
- */
-@JvmInline
-value class CommunalEnabledState(
-    private val disabledReasons: EnumSet<DisabledReason> =
-        EnumSet.noneOf(DisabledReason::class.java)
-) : Diffable<CommunalEnabledState>, Set<DisabledReason> by disabledReasons {
-
-    /** Creates [CommunalEnabledState] with a single reason for being disabled */
-    constructor(reason: DisabledReason) : this(EnumSet.of(reason))
-
-    /** Checks if there are any reasons communal should be disabled. If none, returns true. */
-    val enabled: Boolean
-        get() = isEmpty()
-
-    override fun logDiffs(prevVal: CommunalEnabledState, row: TableRowLogger) {
-        for (reason in DisabledReason.entries) {
-            val newVal = contains(reason)
-            if (newVal != prevVal.contains(reason)) {
-                row.logChange(
-                    columnName = reason.loggingString,
-                    value = newVal,
-                )
-            }
-        }
-    }
-
-    override fun logFull(row: TableRowLogger) {
-        for (reason in DisabledReason.entries) {
-            row.logChange(columnName = reason.loggingString, value = contains(reason))
-        }
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/model/CommunalFeature.kt b/packages/SystemUI/src/com/android/systemui/communal/data/model/CommunalFeature.kt
new file mode 100644
index 0000000..5fb1c4e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/model/CommunalFeature.kt
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2025 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.data.model
+
+import android.annotation.IntDef
+
+@Retention(AnnotationRetention.SOURCE)
+@IntDef(
+    flag = true,
+    prefix = ["FEATURE_"],
+    value = [FEATURE_AUTO_OPEN, FEATURE_MANUAL_OPEN, FEATURE_ENABLED, FEATURE_ALL],
+)
+annotation class CommunalFeature
+
+/** If we should automatically open the hub */
+const val FEATURE_AUTO_OPEN: Int = 1
+
+/** If the user is allowed to manually open the hub */
+const val FEATURE_MANUAL_OPEN: Int = 1 shl 1
+
+/**
+ * If the hub should be considered enabled. If not, it may be cleaned up entirely to reduce memory
+ * footprint.
+ */
+const val FEATURE_ENABLED: Int = 1 shl 2
+
+const val FEATURE_ALL: Int = FEATURE_ENABLED or FEATURE_MANUAL_OPEN or FEATURE_AUTO_OPEN
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/model/SuppressionReason.kt b/packages/SystemUI/src/com/android/systemui/communal/data/model/SuppressionReason.kt
new file mode 100644
index 0000000..de05bed
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/model/SuppressionReason.kt
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2025 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.data.model
+
+sealed interface SuppressionReason {
+    @CommunalFeature val suppressedFeatures: Int
+
+    /** Whether this reason suppresses a particular feature. */
+    fun isSuppressed(@CommunalFeature feature: Int): Boolean {
+        return (suppressedFeatures and feature) != 0
+    }
+
+    /** Suppress hub automatically opening due to Android Auto projection */
+    data object ReasonCarProjection : SuppressionReason {
+        override val suppressedFeatures: Int = FEATURE_AUTO_OPEN
+    }
+
+    /** Suppress hub due to the "When to dream" conditions not being met */
+    data class ReasonWhenToAutoShow(override val suppressedFeatures: Int) : SuppressionReason
+
+    /** Suppress hub due to device policy */
+    data object ReasonDevicePolicy : SuppressionReason {
+        override val suppressedFeatures: Int = FEATURE_ALL
+    }
+
+    /** Suppress hub due to the user disabling the setting */
+    data object ReasonSettingDisabled : SuppressionReason {
+        override val suppressedFeatures: Int = FEATURE_ALL
+    }
+
+    /** Suppress hub due to the user being locked */
+    data object ReasonUserLocked : SuppressionReason {
+        override val suppressedFeatures: Int = FEATURE_ALL
+    }
+
+    /** Suppress hub due the a secondary user being active */
+    data object ReasonSecondaryUser : SuppressionReason {
+        override val suppressedFeatures: Int = FEATURE_ALL
+    }
+
+    /** Suppress hub due to the flag being disabled */
+    data object ReasonFlagDisabled : SuppressionReason {
+        override val suppressedFeatures: Int = FEATURE_ALL
+    }
+
+    /** Suppress hub due to an unknown reason, used as initial state and in tests */
+    data class ReasonUnknown(override val suppressedFeatures: Int = FEATURE_ALL) :
+        SuppressionReason
+}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CarProjectionRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CarProjectionRepository.kt
new file mode 100644
index 0000000..4fe641a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CarProjectionRepository.kt
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2025 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.data.repository
+
+import android.app.UiModeManager
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.util.kotlin.emitOnStart
+import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.asExecutor
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.withContext
+
+interface CarProjectionRepository {
+    /** Whether car projection is active. */
+    val projectionActive: Flow<Boolean>
+
+    /**
+     * Checks the system for the current car projection state.
+     *
+     * @return True if projection is active, false otherwise.
+     */
+    suspend fun isProjectionActive(): Boolean
+}
+
+@SysUISingleton
+class CarProjectionRepositoryImpl
+@Inject
+constructor(
+    private val uiModeManager: UiModeManager,
+    @Background private val bgDispatcher: CoroutineDispatcher,
+) : CarProjectionRepository {
+    override val projectionActive: Flow<Boolean> =
+        conflatedCallbackFlow {
+                val listener =
+                    UiModeManager.OnProjectionStateChangedListener { _, _ -> trySend(Unit) }
+                uiModeManager.addOnProjectionStateChangedListener(
+                    UiModeManager.PROJECTION_TYPE_AUTOMOTIVE,
+                    bgDispatcher.asExecutor(),
+                    listener,
+                )
+                awaitClose { uiModeManager.removeOnProjectionStateChangedListener(listener) }
+            }
+            .emitOnStart()
+            .map { isProjectionActive() }
+            .flowOn(bgDispatcher)
+
+    override suspend fun isProjectionActive(): Boolean =
+        withContext(bgDispatcher) {
+            (uiModeManager.activeProjectionTypes and UiModeManager.PROJECTION_TYPE_AUTOMOTIVE) != 0
+        }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalRepositoryModule.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalRepositoryModule.kt
index 7f137f3..0d590db 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalRepositoryModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalRepositoryModule.kt
@@ -22,4 +22,6 @@
 @Module
 interface CommunalRepositoryModule {
     @Binds fun communalRepository(impl: CommunalSceneRepositoryImpl): CommunalSceneRepository
+
+    @Binds fun carProjectionRepository(impl: CarProjectionRepositoryImpl): CarProjectionRepository
 }
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSettingsRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSettingsRepository.kt
index 4c291a0..6f688d1 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSettingsRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSettingsRepository.kt
@@ -26,12 +26,9 @@
 import com.android.systemui.Flags.communalHub
 import com.android.systemui.Flags.glanceableHubV2
 import com.android.systemui.broadcast.BroadcastDispatcher
-import com.android.systemui.communal.data.model.CommunalEnabledState
-import com.android.systemui.communal.data.model.DisabledReason
-import com.android.systemui.communal.data.model.DisabledReason.DISABLED_REASON_DEVICE_POLICY
-import com.android.systemui.communal.data.model.DisabledReason.DISABLED_REASON_FLAG
-import com.android.systemui.communal.data.model.DisabledReason.DISABLED_REASON_INVALID_USER
-import com.android.systemui.communal.data.model.DisabledReason.DISABLED_REASON_USER_SETTING
+import com.android.systemui.communal.data.model.CommunalFeature
+import com.android.systemui.communal.data.model.FEATURE_ALL
+import com.android.systemui.communal.data.model.SuppressionReason
 import com.android.systemui.communal.data.repository.CommunalSettingsRepositoryModule.Companion.DEFAULT_BACKGROUND_TYPE
 import com.android.systemui.communal.shared.model.CommunalBackgroundType
 import com.android.systemui.communal.shared.model.WhenToDream
@@ -43,22 +40,23 @@
 import com.android.systemui.util.kotlin.emitOnStart
 import com.android.systemui.util.settings.SecureSettings
 import com.android.systemui.util.settings.SettingsProxyExt.observerFlow
-import java.util.EnumSet
 import javax.inject.Inject
 import javax.inject.Named
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.combine
-import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.flowOn
 import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.onStart
 
 interface CommunalSettingsRepository {
-    /** A [CommunalEnabledState] for the specified user. */
-    fun getEnabledState(user: UserInfo): Flow<CommunalEnabledState>
+    /** Whether a particular feature is enabled */
+    fun isEnabled(@CommunalFeature feature: Int): Flow<Boolean>
 
-    fun getScreensaverEnabledState(user: UserInfo): Flow<Boolean>
+    /**
+     * Suppresses the hub with the given reasons. If there are no reasons, the hub will not be
+     * suppressed.
+     */
+    fun setSuppressionReasons(reasons: List<SuppressionReason>)
 
     /**
      * Returns a [WhenToDream] for the specified user, indicating what state the device should be in
@@ -66,6 +64,9 @@
      */
     fun getWhenToDreamState(user: UserInfo): Flow<WhenToDream>
 
+    /** Returns whether glanceable hub is enabled by the current user. */
+    fun getSettingEnabledByUser(user: UserInfo): Flow<Boolean>
+
     /**
      * Returns true if any glanceable hub functionality should be enabled via configs and flags.
      *
@@ -123,6 +124,19 @@
         resources.getBoolean(com.android.internal.R.bool.config_dreamsActivatedOnPosturedByDefault)
     }
 
+    private val _suppressionReasons =
+        MutableStateFlow<List<SuppressionReason>>(
+            // Suppress hub by default until we get an initial update.
+            listOf(SuppressionReason.ReasonUnknown(FEATURE_ALL))
+        )
+
+    override fun isEnabled(@CommunalFeature feature: Int): Flow<Boolean> =
+        _suppressionReasons.map { reasons -> reasons.none { it.isSuppressed(feature) } }
+
+    override fun setSuppressionReasons(reasons: List<SuppressionReason>) {
+        _suppressionReasons.value = reasons
+    }
+
     override fun getFlagEnabled(): Boolean {
         return if (getV2FlagEnabled()) {
             true
@@ -138,44 +152,6 @@
             glanceableHubV2()
     }
 
-    override fun getEnabledState(user: UserInfo): Flow<CommunalEnabledState> {
-        if (!user.isMain) {
-            return flowOf(CommunalEnabledState(DISABLED_REASON_INVALID_USER))
-        }
-        if (!getFlagEnabled()) {
-            return flowOf(CommunalEnabledState(DISABLED_REASON_FLAG))
-        }
-        return combine(
-                getEnabledByUser(user).mapToReason(DISABLED_REASON_USER_SETTING),
-                getAllowedByDevicePolicy(user).mapToReason(DISABLED_REASON_DEVICE_POLICY),
-            ) { reasons ->
-                reasons.filterNotNull()
-            }
-            .map { reasons ->
-                if (reasons.isEmpty()) {
-                    EnumSet.noneOf(DisabledReason::class.java)
-                } else {
-                    EnumSet.copyOf(reasons)
-                }
-            }
-            .map { reasons -> CommunalEnabledState(reasons) }
-            .flowOn(bgDispatcher)
-    }
-
-    override fun getScreensaverEnabledState(user: UserInfo): Flow<Boolean> =
-        secureSettings
-            .observerFlow(userId = user.id, names = arrayOf(Settings.Secure.SCREENSAVER_ENABLED))
-            // Force an update
-            .onStart { emit(Unit) }
-            .map {
-                secureSettings.getIntForUser(
-                    Settings.Secure.SCREENSAVER_ENABLED,
-                    SCREENSAVER_ENABLED_SETTING_DEFAULT,
-                    user.id,
-                ) == 1
-            }
-            .flowOn(bgDispatcher)
-
     override fun getWhenToDreamState(user: UserInfo): Flow<WhenToDream> =
         secureSettings
             .observerFlow(
@@ -247,11 +223,11 @@
                     ?: defaultBackgroundType
             }
 
-    private fun getEnabledByUser(user: UserInfo): Flow<Boolean> =
+    override fun getSettingEnabledByUser(user: UserInfo): Flow<Boolean> =
         secureSettings
             .observerFlow(userId = user.id, names = arrayOf(Settings.Secure.GLANCEABLE_HUB_ENABLED))
             // Force an update
-            .onStart { emit(Unit) }
+            .emitOnStart()
             .map {
                 secureSettings.getIntForUser(
                     Settings.Secure.GLANCEABLE_HUB_ENABLED,
@@ -259,17 +235,13 @@
                     user.id,
                 ) == 1
             }
+            .flowOn(bgDispatcher)
 
     companion object {
         const val GLANCEABLE_HUB_BACKGROUND_SETTING = "glanceable_hub_background"
         private const val ENABLED_SETTING_DEFAULT = 1
-        private const val SCREENSAVER_ENABLED_SETTING_DEFAULT = 0
     }
 }
 
 private fun DevicePolicyManager.areKeyguardWidgetsAllowed(userId: Int): Boolean =
     (getKeyguardDisabledFeatures(null, userId) and KEYGUARD_DISABLE_WIDGETS_ALL) == 0
-
-private fun Flow<Boolean>.mapToReason(reason: DisabledReason) = map { enabled ->
-    if (enabled) null else reason
-}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CarProjectionInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CarProjectionInteractor.kt
new file mode 100644
index 0000000..17b61e1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CarProjectionInteractor.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2025 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.domain.interactor
+
+import com.android.systemui.communal.data.repository.CarProjectionRepository
+import com.android.systemui.dagger.SysUISingleton
+import javax.inject.Inject
+
+@SysUISingleton
+class CarProjectionInteractor @Inject constructor(repository: CarProjectionRepository) {
+    /** Whether car projection is active. */
+    val projectionActive = repository.projectionActive
+}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalAutoOpenInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalAutoOpenInteractor.kt
new file mode 100644
index 0000000..51df333
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalAutoOpenInteractor.kt
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2025 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.domain.interactor
+
+import com.android.systemui.common.domain.interactor.BatteryInteractor
+import com.android.systemui.communal.dagger.CommunalModule.Companion.SWIPE_TO_HUB
+import com.android.systemui.communal.data.model.FEATURE_AUTO_OPEN
+import com.android.systemui.communal.data.model.FEATURE_MANUAL_OPEN
+import com.android.systemui.communal.data.model.SuppressionReason
+import com.android.systemui.communal.posturing.domain.interactor.PosturingInteractor
+import com.android.systemui.communal.shared.model.WhenToDream
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dock.DockManager
+import com.android.systemui.dock.retrieveIsDocked
+import com.android.systemui.util.kotlin.BooleanFlowOperators.allOf
+import com.android.systemui.utils.coroutines.flow.flatMapLatestConflated
+import javax.inject.Inject
+import javax.inject.Named
+import kotlin.coroutines.CoroutineContext
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.map
+
+@SysUISingleton
+class CommunalAutoOpenInteractor
+@Inject
+constructor(
+    communalSettingsInteractor: CommunalSettingsInteractor,
+    @Background private val backgroundContext: CoroutineContext,
+    private val batteryInteractor: BatteryInteractor,
+    private val posturingInteractor: PosturingInteractor,
+    private val dockManager: DockManager,
+    @Named(SWIPE_TO_HUB) private val allowSwipeAlways: Boolean,
+) {
+    val shouldAutoOpen: Flow<Boolean> =
+        communalSettingsInteractor.whenToDream
+            .flatMapLatestConflated { whenToDream ->
+                when (whenToDream) {
+                    WhenToDream.WHILE_CHARGING -> batteryInteractor.isDevicePluggedIn
+                    WhenToDream.WHILE_DOCKED -> {
+                        allOf(batteryInteractor.isDevicePluggedIn, dockManager.retrieveIsDocked())
+                    }
+                    WhenToDream.WHILE_POSTURED -> {
+                        allOf(batteryInteractor.isDevicePluggedIn, posturingInteractor.postured)
+                    }
+                    WhenToDream.NEVER -> flowOf(false)
+                }
+            }
+            .flowOn(backgroundContext)
+
+    val suppressionReason: Flow<SuppressionReason?> =
+        shouldAutoOpen.map { conditionMet ->
+            if (conditionMet) {
+                null
+            } else {
+                var suppressedFeatures = FEATURE_AUTO_OPEN
+                if (!allowSwipeAlways) {
+                    suppressedFeatures = suppressedFeatures or FEATURE_MANUAL_OPEN
+                }
+                SuppressionReason.ReasonWhenToAutoShow(suppressedFeatures)
+            }
+        }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
index 564628d..684c52a 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
@@ -30,13 +30,11 @@
 import com.android.systemui.Flags.communalResponsiveGrid
 import com.android.systemui.Flags.glanceableHubBlurredBackground
 import com.android.systemui.broadcast.BroadcastDispatcher
-import com.android.systemui.common.domain.interactor.BatteryInteractor
 import com.android.systemui.communal.data.repository.CommunalMediaRepository
 import com.android.systemui.communal.data.repository.CommunalSmartspaceRepository
 import com.android.systemui.communal.data.repository.CommunalWidgetRepository
 import com.android.systemui.communal.domain.model.CommunalContentModel
 import com.android.systemui.communal.domain.model.CommunalContentModel.WidgetContent
-import com.android.systemui.communal.posturing.domain.interactor.PosturingInteractor
 import com.android.systemui.communal.shared.model.CommunalBackgroundType
 import com.android.systemui.communal.shared.model.CommunalContentSize
 import com.android.systemui.communal.shared.model.CommunalContentSize.FixedSize.FULL
@@ -45,14 +43,11 @@
 import com.android.systemui.communal.shared.model.CommunalScenes
 import com.android.systemui.communal.shared.model.CommunalWidgetContentModel
 import com.android.systemui.communal.shared.model.EditModeState
-import com.android.systemui.communal.shared.model.WhenToDream
 import com.android.systemui.communal.widgets.EditWidgetsActivityStarter
 import com.android.systemui.communal.widgets.WidgetConfigurator
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Background
-import com.android.systemui.dock.DockManager
-import com.android.systemui.dock.retrieveIsDocked
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
 import com.android.systemui.keyguard.shared.model.Edge
@@ -69,11 +64,8 @@
 import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.settings.UserTracker
 import com.android.systemui.statusbar.phone.ManagedProfileController
-import com.android.systemui.user.domain.interactor.UserLockedInteractor
 import com.android.systemui.util.kotlin.BooleanFlowOperators.allOf
-import com.android.systemui.util.kotlin.BooleanFlowOperators.not
 import com.android.systemui.util.kotlin.emitOnStart
-import com.android.systemui.util.kotlin.isDevicePluggedIn
 import javax.inject.Inject
 import kotlin.time.Duration.Companion.minutes
 import kotlinx.coroutines.CoroutineDispatcher
@@ -125,10 +117,6 @@
     @CommunalLog logBuffer: LogBuffer,
     @CommunalTableLog tableLogBuffer: TableLogBuffer,
     private val managedProfileController: ManagedProfileController,
-    private val batteryInteractor: BatteryInteractor,
-    private val dockManager: DockManager,
-    private val posturingInteractor: PosturingInteractor,
-    private val userLockedInteractor: UserLockedInteractor,
 ) {
     private val logger = Logger(logBuffer, "CommunalInteractor")
 
@@ -162,11 +150,7 @@
 
     /** Whether communal features are enabled and available. */
     val isCommunalAvailable: Flow<Boolean> =
-        allOf(
-                communalSettingsInteractor.isCommunalEnabled,
-                userLockedInteractor.isUserUnlocked(userManager.mainUser),
-                keyguardInteractor.isKeyguardShowing,
-            )
+        allOf(communalSettingsInteractor.isCommunalEnabled, keyguardInteractor.isKeyguardShowing)
             .distinctUntilChanged()
             .onEach { available ->
                 logger.i({ "Communal is ${if (bool1) "" else "un"}available" }) {
@@ -184,37 +168,6 @@
                 replay = 1,
             )
 
-    /**
-     * Whether communal hub should be shown automatically, depending on the user's [WhenToDream]
-     * state.
-     */
-    val shouldShowCommunal: StateFlow<Boolean> =
-        allOf(
-                isCommunalAvailable,
-                communalSettingsInteractor.whenToDream
-                    .flatMapLatest { whenToDream ->
-                        when (whenToDream) {
-                            WhenToDream.NEVER -> flowOf(false)
-
-                            WhenToDream.WHILE_CHARGING -> batteryInteractor.isDevicePluggedIn
-
-                            WhenToDream.WHILE_DOCKED ->
-                                allOf(
-                                    batteryInteractor.isDevicePluggedIn,
-                                    dockManager.retrieveIsDocked(),
-                                )
-
-                            WhenToDream.WHILE_POSTURED ->
-                                allOf(
-                                    batteryInteractor.isDevicePluggedIn,
-                                    posturingInteractor.postured,
-                                )
-                        }
-                    }
-                    .flowOn(bgDispatcher),
-            )
-            .stateIn(scope = bgScope, started = SharingStarted.Eagerly, initialValue = false)
-
     private val _isDisclaimerDismissed = MutableStateFlow(false)
     val isDisclaimerDismissed: Flow<Boolean> = _isDisclaimerDismissed.asStateFlow()
 
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSettingsInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSettingsInteractor.kt
index a0b1261..ae89b39 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSettingsInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSettingsInteractor.kt
@@ -18,17 +18,18 @@
 
 import android.content.pm.UserInfo
 import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
-import com.android.systemui.communal.data.model.CommunalEnabledState
+import com.android.systemui.communal.data.model.FEATURE_AUTO_OPEN
+import com.android.systemui.communal.data.model.FEATURE_ENABLED
+import com.android.systemui.communal.data.model.FEATURE_MANUAL_OPEN
+import com.android.systemui.communal.data.model.SuppressionReason
 import com.android.systemui.communal.data.repository.CommunalSettingsRepository
 import com.android.systemui.communal.shared.model.CommunalBackgroundType
 import com.android.systemui.communal.shared.model.WhenToDream
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Background
-import com.android.systemui.log.dagger.CommunalTableLog
-import com.android.systemui.log.table.TableLogBuffer
-import com.android.systemui.log.table.logDiffsForTable
 import com.android.systemui.settings.UserTracker
 import com.android.systemui.user.domain.interactor.SelectedUserInteractor
+import com.android.systemui.utils.coroutines.flow.flatMapLatestConflated
 import java.util.concurrent.Executor
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineDispatcher
@@ -53,33 +54,43 @@
     private val repository: CommunalSettingsRepository,
     userInteractor: SelectedUserInteractor,
     private val userTracker: UserTracker,
-    @CommunalTableLog tableLogBuffer: TableLogBuffer,
 ) {
-    /** Whether or not communal is enabled for the currently selected user. */
+    /** Whether communal is enabled at all. */
     val isCommunalEnabled: StateFlow<Boolean> =
-        userInteractor.selectedUserInfo
-            .flatMapLatest { user -> repository.getEnabledState(user) }
-            .logDiffsForTable(
-                tableLogBuffer = tableLogBuffer,
-                columnPrefix = "disabledReason",
-                initialValue = CommunalEnabledState(),
-            )
-            .map { model -> model.enabled }
-            // Start this eagerly since the value is accessed synchronously in many places.
+        repository
+            .isEnabled(FEATURE_ENABLED)
             .stateIn(scope = bgScope, started = SharingStarted.Eagerly, initialValue = false)
 
-    /** Whether or not screensaver (dreams) is enabled for the currently selected user. */
-    val isScreensaverEnabled: Flow<Boolean> =
-        userInteractor.selectedUserInfo.flatMapLatest { user ->
-            repository.getScreensaverEnabledState(user)
-        }
+    /** Whether manually opening the hub is enabled */
+    val manualOpenEnabled: StateFlow<Boolean> =
+        repository
+            .isEnabled(FEATURE_MANUAL_OPEN)
+            .stateIn(scope = bgScope, started = SharingStarted.Eagerly, initialValue = false)
+
+    /** Whether auto-opening the hub is enabled */
+    val autoOpenEnabled: StateFlow<Boolean> =
+        repository
+            .isEnabled(FEATURE_AUTO_OPEN)
+            .stateIn(scope = bgScope, started = SharingStarted.Eagerly, initialValue = false)
 
     /** When to dream for the currently selected user. */
     val whenToDream: Flow<WhenToDream> =
-        userInteractor.selectedUserInfo.flatMapLatest { user ->
+        userInteractor.selectedUserInfo.flatMapLatestConflated { user ->
             repository.getWhenToDreamState(user)
         }
 
+    /** Whether communal hub is allowed by device policy for the current user */
+    val allowedForCurrentUserByDevicePolicy: Flow<Boolean> =
+        userInteractor.selectedUserInfo.flatMapLatestConflated { user ->
+            repository.getAllowedByDevicePolicy(user)
+        }
+
+    /** Whether the hub is enabled for the current user */
+    val settingEnabledForCurrentUser: Flow<Boolean> =
+        userInteractor.selectedUserInfo.flatMapLatestConflated { user ->
+            repository.getSettingEnabledByUser(user)
+        }
+
     /**
      * Returns true if any glanceable hub functionality should be enabled via configs and flags.
      *
@@ -109,6 +120,14 @@
      */
     fun isV2FlagEnabled(): Boolean = repository.getV2FlagEnabled()
 
+    /**
+     * Suppresses the hub with the given reasons. If there are no reasons, the hub will not be
+     * suppressed.
+     */
+    fun setSuppressionReasons(reasons: List<SuppressionReason>) {
+        repository.setSuppressionReasons(reasons)
+    }
+
     /** The type of background to use for the hub. Used to experiment with different backgrounds */
     val communalBackground: Flow<CommunalBackgroundType> =
         userInteractor.selectedUserInfo
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/suppression/FlowExt.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/suppression/FlowExt.kt
new file mode 100644
index 0000000..a10e90f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/suppression/FlowExt.kt
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2025 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.domain.suppression
+
+import com.android.systemui.communal.data.model.SuppressionReason
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.map
+
+fun Flow<Boolean>.mapToReasonIfNotAllowed(reason: SuppressionReason): Flow<SuppressionReason?> =
+    this.map { allowed -> if (allowed) null else reason }
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/suppression/dagger/CommunalSuppressionModule.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/suppression/dagger/CommunalSuppressionModule.kt
new file mode 100644
index 0000000..c62d77e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/suppression/dagger/CommunalSuppressionModule.kt
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2025 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.domain.suppression.dagger
+
+import com.android.systemui.Flags.glanceableHubV2
+import com.android.systemui.communal.data.model.SuppressionReason
+import com.android.systemui.communal.domain.interactor.CarProjectionInteractor
+import com.android.systemui.communal.domain.interactor.CommunalAutoOpenInteractor
+import com.android.systemui.communal.domain.interactor.CommunalSettingsInteractor
+import com.android.systemui.communal.domain.suppression.mapToReasonIfNotAllowed
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor
+import com.android.systemui.user.domain.interactor.UserLockedInteractor
+import com.android.systemui.util.kotlin.BooleanFlowOperators.not
+import dagger.Module
+import dagger.Provides
+import dagger.multibindings.IntoSet
+import dagger.multibindings.Multibinds
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.map
+
+@Module
+interface CommunalSuppressionModule {
+    /**
+     * A set of reasons why communal may be suppressed. Ensures that this can be injected even if
+     * it's empty.
+     */
+    @Multibinds fun suppressorSet(): Set<Flow<SuppressionReason?>>
+
+    companion object {
+        @Provides
+        @IntoSet
+        fun provideCarProjectionSuppressor(
+            interactor: CarProjectionInteractor
+        ): Flow<SuppressionReason?> {
+            if (!glanceableHubV2()) {
+                return flowOf(null)
+            }
+            return not(interactor.projectionActive)
+                .mapToReasonIfNotAllowed(SuppressionReason.ReasonCarProjection)
+        }
+
+        @Provides
+        @IntoSet
+        fun provideDevicePolicySuppressor(
+            interactor: CommunalSettingsInteractor
+        ): Flow<SuppressionReason?> {
+            return interactor.allowedForCurrentUserByDevicePolicy.mapToReasonIfNotAllowed(
+                SuppressionReason.ReasonDevicePolicy
+            )
+        }
+
+        @Provides
+        @IntoSet
+        fun provideSettingDisabledSuppressor(
+            interactor: CommunalSettingsInteractor
+        ): Flow<SuppressionReason?> {
+            return interactor.settingEnabledForCurrentUser.mapToReasonIfNotAllowed(
+                SuppressionReason.ReasonSettingDisabled
+            )
+        }
+
+        @Provides
+        @IntoSet
+        fun bindUserLockedSuppressor(interactor: UserLockedInteractor): Flow<SuppressionReason?> {
+            return interactor.currentUserUnlocked.mapToReasonIfNotAllowed(
+                SuppressionReason.ReasonUserLocked
+            )
+        }
+
+        @Provides
+        @IntoSet
+        fun provideAutoOpenSuppressor(
+            interactor: CommunalAutoOpenInteractor
+        ): Flow<SuppressionReason?> {
+            return interactor.suppressionReason
+        }
+
+        @Provides
+        @IntoSet
+        fun provideMainUserSuppressor(
+            interactor: SelectedUserInteractor
+        ): Flow<SuppressionReason?> {
+            return interactor.selectedUserInfo
+                .map { it.isMain }
+                .mapToReasonIfNotAllowed(SuppressionReason.ReasonSecondaryUser)
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
index 62a98d7..857fa5c 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
@@ -369,12 +369,10 @@
 
     val swipeToHubEnabled: Flow<Boolean> by lazy {
         val inAllowedDeviceState =
-            if (swipeToHub) {
-                MutableStateFlow(true)
-            } else if (v2FlagEnabled()) {
-                communalInteractor.shouldShowCommunal
+            if (v2FlagEnabled()) {
+                communalSettingsInteractor.manualOpenEnabled
             } else {
-                MutableStateFlow(false)
+                MutableStateFlow(swipeToHub)
             }
 
         if (v2FlagEnabled()) {
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/ui/viewmodel/DreamViewModel.kt b/packages/SystemUI/src/com/android/systemui/dreams/ui/viewmodel/DreamViewModel.kt
index a7c078f..36b75c6 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/ui/viewmodel/DreamViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/dreams/ui/viewmodel/DreamViewModel.kt
@@ -61,7 +61,7 @@
     fun startTransitionFromDream() {
         val showGlanceableHub =
             if (communalSettingsInteractor.isV2FlagEnabled()) {
-                communalInteractor.shouldShowCommunal.value
+                communalSettingsInteractor.autoOpenEnabled.value
             } else {
                 communalInteractor.isCommunalEnabled.value &&
                     !keyguardUpdateMonitor.isEncryptedOrLockdown(userTracker.userId)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt
index ef06a85..54af8f5 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt
@@ -20,7 +20,6 @@
 import android.util.Log
 import com.android.app.animation.Interpolators
 import com.android.app.tracing.coroutines.launchTraced as launch
-import com.android.systemui.communal.domain.interactor.CommunalInteractor
 import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor
 import com.android.systemui.communal.domain.interactor.CommunalSettingsInteractor
 import com.android.systemui.communal.shared.model.CommunalScenes
@@ -60,7 +59,6 @@
     private val wakeToGoneInteractor: KeyguardWakeDirectlyToGoneInteractor,
     private val communalSettingsInteractor: CommunalSettingsInteractor,
     private val communalSceneInteractor: CommunalSceneInteractor,
-    private val communalInteractor: CommunalInteractor,
 ) :
     TransitionInteractor(
         fromState = KeyguardState.AOD,
@@ -110,7 +108,7 @@
                     val isKeyguardOccludedLegacy = keyguardInteractor.isKeyguardOccluded.value
                     val biometricUnlockMode = keyguardInteractor.biometricUnlockState.value.mode
                     val primaryBouncerShowing = keyguardInteractor.primaryBouncerShowing.value
-                    val shouldShowCommunal = communalInteractor.shouldShowCommunal.value
+                    val shouldShowCommunal = communalSettingsInteractor.autoOpenEnabled.value
 
                     if (!maybeHandleInsecurePowerGesture()) {
                         val shouldTransitionToLockscreen =
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 6f5f662..1fc4108 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
@@ -153,7 +153,7 @@
                 .filterRelevantKeyguardStateAnd { isAwake -> isAwake }
                 .sample(
                     communalInteractor.isCommunalAvailable,
-                    communalInteractor.shouldShowCommunal,
+                    communalSettingsInteractor.autoOpenEnabled,
                 )
                 .collect { (_, isCommunalAvailable, shouldShowCommunal) ->
                     val isKeyguardOccludedLegacy = keyguardInteractor.isKeyguardOccluded.value
@@ -209,7 +209,7 @@
             powerInteractor.detailedWakefulness
                 .filterRelevantKeyguardStateAnd { it.isAwake() }
                 .sample(
-                    communalInteractor.shouldShowCommunal,
+                    communalSettingsInteractor.autoOpenEnabled,
                     communalInteractor.isCommunalAvailable,
                     keyguardInteractor.biometricUnlockState,
                     wakeToGoneInteractor.canWakeDirectlyToGone,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
index 0fb98ff..3b1b6fc 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
@@ -115,7 +115,7 @@
                 powerInteractor.isAwake
                     .debounce(50L)
                     .filterRelevantKeyguardStateAnd { isAwake -> isAwake }
-                    .sample(communalInteractor.shouldShowCommunal)
+                    .sample(communalSettingsInteractor.autoOpenEnabled)
                     .collect { shouldShowCommunal ->
                         if (shouldShowCommunal) {
                             // This case handles tapping the power button to transition through
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
index a01dc02..f8c7a86 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
@@ -20,7 +20,6 @@
 import android.util.MathUtils
 import com.android.app.animation.Interpolators
 import com.android.app.tracing.coroutines.launchTraced as launch
-import com.android.systemui.communal.domain.interactor.CommunalInteractor
 import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor
 import com.android.systemui.communal.domain.interactor.CommunalSettingsInteractor
 import com.android.systemui.communal.shared.model.CommunalScenes
@@ -69,7 +68,6 @@
     private val shadeRepository: ShadeRepository,
     powerInteractor: PowerInteractor,
     private val communalSettingsInteractor: CommunalSettingsInteractor,
-    private val communalInteractor: CommunalInteractor,
     private val communalSceneInteractor: CommunalSceneInteractor,
     private val swipeToDismissInteractor: SwipeToDismissInteractor,
     keyguardOcclusionInteractor: KeyguardOcclusionInteractor,
@@ -355,7 +353,7 @@
 
     private fun listenForLockscreenToGlanceableHubV2() {
         scope.launch {
-            communalInteractor.shouldShowCommunal
+            communalSettingsInteractor.autoOpenEnabled
                 .filterRelevantKeyguardStateAnd { shouldShow -> shouldShow }
                 .collect {
                     communalSceneInteractor.changeScene(
diff --git a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserLockedInteractor.kt b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserLockedInteractor.kt
index 3bd8af6..6657c42 100644
--- a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserLockedInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserLockedInteractor.kt
@@ -20,6 +20,7 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.user.data.repository.UserRepository
+import com.android.systemui.utils.coroutines.flow.flatMapLatestConflated
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.flow.Flow
@@ -31,7 +32,14 @@
 constructor(
     @Background val backgroundDispatcher: CoroutineDispatcher,
     val userRepository: UserRepository,
+    val selectedUserInteractor: SelectedUserInteractor,
 ) {
+    /** Whether the current user is unlocked */
+    val currentUserUnlocked: Flow<Boolean> =
+        selectedUserInteractor.selectedUserInfo.flatMapLatestConflated { user ->
+            isUserUnlocked(user.userHandle)
+        }
+
     fun isUserUnlocked(userHandle: UserHandle?): Flow<Boolean> =
         userRepository.isUserUnlocked(userHandle).flowOn(backgroundDispatcher)
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/CarProjectionRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/CarProjectionRepositoryKosmos.kt
new file mode 100644
index 0000000..130c298
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/CarProjectionRepositoryKosmos.kt
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2025 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.data.repository
+
+import com.android.systemui.kosmos.Kosmos
+
+val Kosmos.carProjectionRepository by
+    Kosmos.Fixture<CarProjectionRepository> { FakeCarProjectionRepository() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCarProjectionRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCarProjectionRepository.kt
new file mode 100644
index 0000000..4042342
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCarProjectionRepository.kt
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2025 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.data.repository
+
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+class FakeCarProjectionRepository : CarProjectionRepository {
+    private val _projectionActive = MutableStateFlow(false)
+    override val projectionActive: Flow<Boolean> = _projectionActive.asStateFlow()
+
+    override suspend fun isProjectionActive(): Boolean {
+        return _projectionActive.value
+    }
+
+    fun setProjectionActive(active: Boolean) {
+        _projectionActive.value = active
+    }
+}
+
+val CarProjectionRepository.fake
+    get() = this as FakeCarProjectionRepository
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CarProjectionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CarProjectionInteractorKosmos.kt
new file mode 100644
index 0000000..23bbe36
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CarProjectionInteractorKosmos.kt
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2025 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.domain.interactor
+
+import com.android.systemui.communal.data.repository.carProjectionRepository
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+
+val Kosmos.carProjectionInteractor by Fixture { CarProjectionInteractor(carProjectionRepository) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalAutoOpenInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalAutoOpenInteractorKosmos.kt
new file mode 100644
index 0000000..5735cf8
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalAutoOpenInteractorKosmos.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2025 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.domain.interactor
+
+import com.android.systemui.common.domain.interactor.batteryInteractor
+import com.android.systemui.communal.posturing.domain.interactor.posturingInteractor
+import com.android.systemui.dock.dockManager
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.kosmos.backgroundCoroutineContext
+
+val Kosmos.communalAutoOpenInteractor by Fixture {
+    CommunalAutoOpenInteractor(
+        communalSettingsInteractor = communalSettingsInteractor,
+        backgroundContext = backgroundCoroutineContext,
+        batteryInteractor = batteryInteractor,
+        posturingInteractor = posturingInteractor,
+        dockManager = dockManager,
+        allowSwipeAlways = false,
+    )
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt
index b8b2ec5..316fcbb 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt
@@ -16,17 +16,14 @@
 
 package com.android.systemui.communal.domain.interactor
 
-import android.content.pm.UserInfo
 import android.content.testableContext
 import android.os.userManager
 import com.android.systemui.broadcast.broadcastDispatcher
-import com.android.systemui.common.domain.interactor.batteryInteractor
+import com.android.systemui.communal.data.model.SuppressionReason
 import com.android.systemui.communal.data.repository.communalMediaRepository
 import com.android.systemui.communal.data.repository.communalSmartspaceRepository
 import com.android.systemui.communal.data.repository.communalWidgetRepository
-import com.android.systemui.communal.posturing.domain.interactor.posturingInteractor
 import com.android.systemui.communal.widgets.EditWidgetsActivityStarter
-import com.android.systemui.dock.dockManager
 import com.android.systemui.flags.Flags
 import com.android.systemui.flags.fakeFeatureFlagsClassic
 import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
@@ -39,13 +36,9 @@
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.log.logcatLogBuffer
 import com.android.systemui.plugins.activityStarter
-import com.android.systemui.res.R
 import com.android.systemui.scene.domain.interactor.sceneInteractor
 import com.android.systemui.settings.userTracker
 import com.android.systemui.statusbar.phone.fakeManagedProfileController
-import com.android.systemui.user.data.repository.FakeUserRepository
-import com.android.systemui.user.data.repository.fakeUserRepository
-import com.android.systemui.user.domain.interactor.userLockedInteractor
 import com.android.systemui.util.mockito.mock
 
 val Kosmos.communalInteractor by Fixture {
@@ -70,10 +63,6 @@
         logBuffer = logcatLogBuffer("CommunalInteractor"),
         tableLogBuffer = mock(),
         managedProfileController = fakeManagedProfileController,
-        batteryInteractor = batteryInteractor,
-        dockManager = dockManager,
-        posturingInteractor = posturingInteractor,
-        userLockedInteractor = userLockedInteractor,
     )
 }
 
@@ -86,28 +75,28 @@
     )
 }
 
-suspend fun Kosmos.setCommunalEnabled(enabled: Boolean): UserInfo {
+fun Kosmos.setCommunalEnabled(enabled: Boolean) {
     fakeFeatureFlagsClassic.set(Flags.COMMUNAL_SERVICE_ENABLED, enabled)
-    return if (enabled) {
-        fakeUserRepository.asMainUser()
-    } else {
-        fakeUserRepository.asDefaultUser()
-    }
+    val suppressionReasons =
+        if (enabled) {
+            emptyList()
+        } else {
+            listOf(SuppressionReason.ReasonUnknown())
+        }
+    communalSettingsInteractor.setSuppressionReasons(suppressionReasons)
 }
 
-suspend fun Kosmos.setCommunalV2Enabled(enabled: Boolean): UserInfo {
+fun Kosmos.setCommunalV2Enabled(enabled: Boolean) {
     setCommunalV2ConfigEnabled(enabled)
     return setCommunalEnabled(enabled)
 }
 
-suspend fun Kosmos.setCommunalAvailable(available: Boolean): UserInfo {
-    val user = setCommunalEnabled(available)
+fun Kosmos.setCommunalAvailable(available: Boolean) {
+    setCommunalEnabled(available)
     fakeKeyguardRepository.setKeyguardShowing(available)
-    fakeUserRepository.setUserUnlocked(FakeUserRepository.MAIN_USER_ID, available)
-    return user
 }
 
-suspend fun Kosmos.setCommunalV2Available(available: Boolean): UserInfo {
+fun Kosmos.setCommunalV2Available(available: Boolean) {
     setCommunalV2ConfigEnabled(available)
-    return setCommunalAvailable(available)
+    setCommunalAvailable(available)
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalSettingsInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalSettingsInteractorKosmos.kt
index fb983f7..d2fbb51 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalSettingsInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalSettingsInteractorKosmos.kt
@@ -24,7 +24,6 @@
 import com.android.systemui.kosmos.testDispatcher
 import com.android.systemui.settings.userTracker
 import com.android.systemui.user.domain.interactor.selectedUserInteractor
-import com.android.systemui.util.mockito.mock
 
 val Kosmos.communalSettingsInteractor by Fixture {
     CommunalSettingsInteractor(
@@ -34,6 +33,5 @@
         repository = communalSettingsRepository,
         userInteractor = selectedUserInteractor,
         userTracker = userTracker,
-        tableLogBuffer = mock(),
     )
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/dock/DockManagerFake.java b/packages/SystemUI/tests/utils/src/com/android/systemui/dock/DockManagerFake.java
deleted file mode 100644
index b99310b..0000000
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/dock/DockManagerFake.java
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * Copyright (C) 2018 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.dock;
-
-/**
- * A rudimentary fake for DockManager.
- */
-public class DockManagerFake implements DockManager {
-    DockEventListener mCallback;
-    AlignmentStateListener mAlignmentListener;
-    private boolean mDocked;
-
-    @Override
-    public void addListener(DockEventListener callback) {
-        this.mCallback = callback;
-    }
-
-    @Override
-    public void removeListener(DockEventListener callback) {
-        this.mCallback = null;
-    }
-
-    @Override
-    public void addAlignmentStateListener(AlignmentStateListener listener) {
-        mAlignmentListener = listener;
-    }
-
-    @Override
-    public void removeAlignmentStateListener(AlignmentStateListener listener) {
-        mAlignmentListener = listener;
-    }
-
-    @Override
-    public boolean isDocked() {
-        return mDocked;
-    }
-
-    /** Sets the docked state */
-    public void setIsDocked(boolean docked) {
-        mDocked = docked;
-    }
-
-    @Override
-    public boolean isHidden() {
-        return false;
-    }
-
-    /** Notifies callbacks of dock state change */
-    public void setDockEvent(int event) {
-        mCallback.onEvent(event);
-    }
-}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/dock/DockManagerFake.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/dock/DockManagerFake.kt
new file mode 100644
index 0000000..6a43c40
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/dock/DockManagerFake.kt
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2025 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.dock
+
+import com.android.systemui.dock.DockManager.AlignmentStateListener
+
+/** A rudimentary fake for DockManager. */
+class DockManagerFake : DockManager {
+    private val callbacks = mutableSetOf<DockManager.DockEventListener>()
+    private val alignmentListeners = mutableSetOf<AlignmentStateListener>()
+    private var docked = false
+
+    override fun addListener(callback: DockManager.DockEventListener) {
+        callbacks.add(callback)
+    }
+
+    override fun removeListener(callback: DockManager.DockEventListener) {
+        callbacks.remove(callback)
+    }
+
+    override fun addAlignmentStateListener(listener: AlignmentStateListener) {
+        alignmentListeners.add(listener)
+    }
+
+    override fun removeAlignmentStateListener(listener: AlignmentStateListener) {
+        alignmentListeners.remove(listener)
+    }
+
+    override fun isDocked(): Boolean {
+        return docked
+    }
+
+    /** Sets the docked state */
+    fun setIsDocked(docked: Boolean) {
+        this.docked = docked
+    }
+
+    override fun isHidden(): Boolean {
+        return false
+    }
+
+    /** Notifies callbacks of dock state change */
+    fun setDockEvent(event: Int) {
+        for (callback in callbacks) {
+            callback.onEvent(event)
+        }
+    }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorKosmos.kt
index bdfa875..9b0a983 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorKosmos.kt
@@ -16,7 +16,6 @@
 
 package com.android.systemui.keyguard.domain.interactor
 
-import com.android.systemui.communal.domain.interactor.communalInteractor
 import com.android.systemui.communal.domain.interactor.communalSceneInteractor
 import com.android.systemui.communal.domain.interactor.communalSettingsInteractor
 import com.android.systemui.deviceentry.data.repository.deviceEntryRepository
@@ -43,6 +42,5 @@
             wakeToGoneInteractor = keyguardWakeDirectlyToGoneInteractor,
             communalSettingsInteractor = communalSettingsInteractor,
             communalSceneInteractor = communalSceneInteractor,
-            communalInteractor = communalInteractor,
         )
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorKosmos.kt
index 985044c..511bede 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorKosmos.kt
@@ -16,7 +16,6 @@
 
 package com.android.systemui.keyguard.domain.interactor
 
-import com.android.systemui.communal.domain.interactor.communalInteractor
 import com.android.systemui.communal.domain.interactor.communalSceneInteractor
 import com.android.systemui.communal.domain.interactor.communalSettingsInteractor
 import com.android.systemui.keyguard.data.repository.keyguardTransitionRepository
@@ -42,7 +41,6 @@
             communalSettingsInteractor = communalSettingsInteractor,
             swipeToDismissInteractor = swipeToDismissInteractor,
             keyguardOcclusionInteractor = keyguardOcclusionInteractor,
-            communalInteractor = communalInteractor,
             communalSceneInteractor = communalSceneInteractor,
         )
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/user/domain/interactor/UserLockedInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/user/domain/interactor/UserLockedInteractorKosmos.kt
index 933c351..6bb908a 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/user/domain/interactor/UserLockedInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/user/domain/interactor/UserLockedInteractorKosmos.kt
@@ -22,5 +22,9 @@
 
 val Kosmos.userLockedInteractor by
     Kosmos.Fixture {
-        UserLockedInteractor(backgroundDispatcher = testDispatcher, userRepository = userRepository)
+        UserLockedInteractor(
+            backgroundDispatcher = testDispatcher,
+            userRepository = userRepository,
+            selectedUserInteractor = selectedUserInteractor,
+        )
     }