Merge "Migrate rotation lock tile" into main
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/camera/data/repository/CameraAutoRotateRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/camera/data/repository/CameraAutoRotateRepositoryImplTest.kt
new file mode 100644
index 0000000..9cfa572
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/camera/data/repository/CameraAutoRotateRepositoryImplTest.kt
@@ -0,0 +1,125 @@
+/*
+ * 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.camera.data.repository
+
+import android.os.UserHandle
+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.coroutines.collectValues
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.util.settings.fakeSettings
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@android.platform.test.annotations.EnabledOnRavenwood
+class CameraAutoRotateRepositoryImplTest : SysuiTestCase() {
+    private val kosmos = Kosmos()
+    private val testScope = kosmos.testScope
+    private val settings = kosmos.fakeSettings
+    private val testUser = UserHandle.of(1)
+
+    private val underTest =
+        CameraAutoRotateRepositoryImpl(settings, testScope.testScheduler, testScope.backgroundScope)
+
+    /** 3 changes => 3 change signals + 1 signal emitted at start => 4 signals */
+    @Test
+    fun isCameraAutoRotateSettingEnabled_3times() =
+        testScope.runTest {
+            settings.putIntForUser(SETTING_NAME, DISABLE, testUser.identifier)
+            val isCameraAutoRotateSettingEnabled by
+                collectValues(underTest.isCameraAutoRotateSettingEnabled(testUser))
+            runCurrent()
+            assertThat(isCameraAutoRotateSettingEnabled.last()).isFalse()
+
+            settings.putIntForUser(SETTING_NAME, ENABLE, testUser.identifier)
+            runCurrent()
+            assertThat(isCameraAutoRotateSettingEnabled.last()).isTrue()
+
+            settings.putIntForUser(SETTING_NAME, DISABLE, testUser.identifier)
+            runCurrent()
+            assertThat(isCameraAutoRotateSettingEnabled.last()).isFalse()
+
+            settings.putIntForUser(SETTING_NAME, ENABLE, testUser.identifier)
+            runCurrent()
+            assertThat(isCameraAutoRotateSettingEnabled.last()).isTrue()
+
+            assertThat(isCameraAutoRotateSettingEnabled).hasSize(4)
+        }
+
+    @Test
+    fun isCameraAutoRotateSettingEnabled_emitsOnStart() =
+        testScope.runTest {
+            val isCameraAutoRotateSettingEnabled: List<Boolean> by
+                collectValues(underTest.isCameraAutoRotateSettingEnabled(testUser))
+
+            runCurrent()
+
+            assertThat(isCameraAutoRotateSettingEnabled).hasSize(1)
+        }
+
+    /** 0 for 0 changes + 1 signal emitted on start => 1 signal */
+    @Test
+    fun isCameraAutoRotateSettingEnabled_0Times() =
+        testScope.runTest {
+            settings.putIntForUser(SETTING_NAME, DISABLE, testUser.identifier)
+            val isCameraAutoRotateSettingEnabled: List<Boolean> by
+                collectValues(underTest.isCameraAutoRotateSettingEnabled(testUser))
+            runCurrent()
+
+            settings.putIntForUser(SETTING_NAME, DISABLE, testUser.identifier)
+            runCurrent()
+
+            assertThat(isCameraAutoRotateSettingEnabled).hasSize(1)
+            assertThat(isCameraAutoRotateSettingEnabled[0]).isFalse()
+        }
+
+    /** Maintain that flows are cached by user */
+    @Test
+    fun sameUserCallsIsCameraAutoRotateSettingEnabledTwice_getsSameFlow() =
+        testScope.runTest {
+            val flow1 = underTest.isCameraAutoRotateSettingEnabled(testUser)
+            val flow2 = underTest.isCameraAutoRotateSettingEnabled(testUser)
+
+            assertThat(flow1).isEqualTo(flow2)
+        }
+
+    @Test
+    fun differentUsersCallIsCameraAutoRotateSettingEnabled_getDifferentFlow() =
+        testScope.runTest {
+            val user2 = UserHandle.of(2)
+            val flow1 = underTest.isCameraAutoRotateSettingEnabled(testUser)
+            val flow2 = underTest.isCameraAutoRotateSettingEnabled(user2)
+
+            assertThat(flow1).isNotEqualTo(flow2)
+        }
+
+    private companion object {
+        private const val SETTING_NAME = Settings.Secure.CAMERA_AUTOROTATE
+        private const val DISABLE = 0
+        private const val ENABLE = 1
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/camera/data/repository/CameraSensorPrivacyRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/camera/data/repository/CameraSensorPrivacyRepositoryImplTest.kt
new file mode 100644
index 0000000..29de58e
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/camera/data/repository/CameraSensorPrivacyRepositoryImplTest.kt
@@ -0,0 +1,104 @@
+/*
+ * 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.camera.data.repository
+
+import android.hardware.SensorPrivacyManager
+import android.os.UserHandle
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.util.mockito.mock
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.ArgumentMatchers
+import org.mockito.Mockito
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@android.platform.test.annotations.EnabledOnRavenwood
+class CameraSensorPrivacyRepositoryImplTest : SysuiTestCase() {
+    private val kosmos = Kosmos()
+    private val testScope = kosmos.testScope
+    private val testUser = UserHandle.of(1)
+    private val privacyManager = mock<SensorPrivacyManager>()
+    private val underTest =
+        CameraSensorPrivacyRepositoryImpl(
+            testScope.testScheduler,
+            testScope.backgroundScope,
+            privacyManager
+        )
+
+    @Test
+    fun isEnabled_2TimesForSameUserReturnsCachedFlow() =
+        testScope.runTest {
+            val flow1 = underTest.isEnabled(testUser)
+            val flow2 = underTest.isEnabled(testUser)
+            runCurrent()
+
+            assertThat(flow1).isEqualTo(flow2)
+        }
+
+    @Test
+    fun isEnabled_2TimesForDifferentUsersReturnsTwoDifferentFlows() =
+        testScope.runTest {
+            val user2 = UserHandle.of(2)
+
+            val flow1 = underTest.isEnabled(testUser)
+            val flow2 = underTest.isEnabled(user2)
+            runCurrent()
+
+            assertThat(flow1).isNotEqualTo(flow2)
+        }
+
+    @Test
+    fun isEnabled_dataMatchesSensorPrivacyManager() =
+        testScope.runTest {
+            val isEnabled = collectLastValue(underTest.isEnabled(testUser))
+
+            val captor =
+                ArgumentCaptor.forClass(
+                    SensorPrivacyManager.OnSensorPrivacyChangedListener::class.java
+                )
+            runCurrent()
+            assertThat(isEnabled()).isEqualTo(false)
+
+            Mockito.verify(privacyManager)
+                .addSensorPrivacyListener(
+                    ArgumentMatchers.eq(SensorPrivacyManager.Sensors.CAMERA),
+                    ArgumentMatchers.eq(testUser.identifier),
+                    captor.capture()
+                )
+            val sensorPrivacyCallback = captor.value!!
+
+            sensorPrivacyCallback.onSensorPrivacyChanged(SensorPrivacyManager.Sensors.CAMERA, true)
+            runCurrent()
+            assertThat(isEnabled()).isEqualTo(true)
+
+            sensorPrivacyCallback.onSensorPrivacyChanged(SensorPrivacyManager.Sensors.CAMERA, false)
+            runCurrent()
+            assertThat(isEnabled()).isEqualTo(false)
+        }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/camera/data/repository/FakeCameraAutoRotateRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/camera/data/repository/FakeCameraAutoRotateRepositoryTest.kt
new file mode 100644
index 0000000..f75e036
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/camera/data/repository/FakeCameraAutoRotateRepositoryTest.kt
@@ -0,0 +1,78 @@
+/*
+ * 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.camera.data.repository
+
+import android.os.UserHandle
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectValues
+import com.android.systemui.kosmos.Kosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@android.platform.test.annotations.EnabledOnRavenwood
+class FakeCameraAutoRotateRepositoryTest : SysuiTestCase() {
+    private val kosmos = Kosmos()
+    private val underTest = kosmos.fakeCameraAutoRotateRepository
+    private val testUser = UserHandle.of(1)
+
+    @Test
+    fun isCameraAutoRotateSettingEnabled_emitsFalseOnStart() = runTest {
+        val isCameraAutoRotateSettingEnabled by
+            collectValues(underTest.isCameraAutoRotateSettingEnabled(testUser))
+
+        assertThat(isCameraAutoRotateSettingEnabled).hasSize(1)
+        assertThat(isCameraAutoRotateSettingEnabled.first()).isFalse()
+    }
+
+    /**
+     * The value explicitly set in this test is not distinct, therefore only 1 value is collected.
+     */
+    @Test
+    fun isCameraAutoRotateSettingEnabled_emitsDistinctValueOnly() = runTest {
+        val isCameraAutoRotateSettingEnabled by
+            collectValues(underTest.isCameraAutoRotateSettingEnabled(testUser))
+        underTest.setEnabled(testUser, false)
+        runCurrent()
+
+        assertThat(isCameraAutoRotateSettingEnabled).hasSize(1)
+        assertThat(isCameraAutoRotateSettingEnabled.first()).isFalse()
+    }
+
+    @Test
+    fun isCameraAutoRotateSettingEnabled_canSetValue3Times() = runTest {
+        val isCameraAutoRotateSettingEnabled by
+            collectValues(underTest.isCameraAutoRotateSettingEnabled(testUser))
+        runCurrent()
+        underTest.setEnabled(testUser, true)
+        runCurrent()
+        underTest.setEnabled(testUser, false)
+        runCurrent()
+        underTest.setEnabled(testUser, true)
+        runCurrent()
+        assertThat(isCameraAutoRotateSettingEnabled).hasSize(4)
+        assertThat(isCameraAutoRotateSettingEnabled.last()).isTrue()
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/camera/data/repository/FakeCameraSensorPrivacyRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/camera/data/repository/FakeCameraSensorPrivacyRepositoryTest.kt
new file mode 100644
index 0000000..7fa1be3
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/camera/data/repository/FakeCameraSensorPrivacyRepositoryTest.kt
@@ -0,0 +1,75 @@
+/*
+ * 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.camera.data.repository
+
+import android.os.UserHandle
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectValues
+import com.android.systemui.kosmos.Kosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@android.platform.test.annotations.EnabledOnRavenwood
+class FakeCameraSensorPrivacyRepositoryTest : SysuiTestCase() {
+    private val kosmos = Kosmos()
+    private val underTest = kosmos.fakeCameraSensorPrivacyRepository
+    private val testUser = UserHandle.of(1)
+
+    @Test
+    fun isCameraSensorPrivacyEnabled_emitsFalseOnStart() = runTest {
+        val isCameraSensorPrivacySettingEnabled by collectValues(underTest.isEnabled(testUser))
+
+        assertThat(isCameraSensorPrivacySettingEnabled).hasSize(1)
+        assertThat(isCameraSensorPrivacySettingEnabled.first()).isFalse()
+    }
+
+    /**
+     * The value explicitly set in this test is not distinct, therefore only 1 value is collected.
+     */
+    @Test
+    fun isCameraSensorPrivacyEnabled_emitsDistinctValueOnly() = runTest {
+        val isCameraSensorPrivacySettingEnabled by collectValues(underTest.isEnabled(testUser))
+        underTest.setEnabled(testUser, false)
+        runCurrent()
+
+        assertThat(isCameraSensorPrivacySettingEnabled).hasSize(1)
+        assertThat(isCameraSensorPrivacySettingEnabled.first()).isFalse()
+    }
+
+    @Test
+    fun isCameraSensorPrivacyEnabled_canSetValue3Times() = runTest {
+        val isCameraSensorPrivacySettingEnabled by collectValues(underTest.isEnabled(testUser))
+        runCurrent()
+        underTest.setEnabled(testUser, true)
+        runCurrent()
+        underTest.setEnabled(testUser, false)
+        runCurrent()
+        underTest.setEnabled(testUser, true)
+        runCurrent()
+        assertThat(isCameraSensorPrivacySettingEnabled).hasSize(4)
+        assertThat(isCameraSensorPrivacySettingEnabled.last()).isTrue()
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/rotation/domain/interactor/RotationLockTileDataInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/rotation/domain/interactor/RotationLockTileDataInteractorTest.kt
new file mode 100644
index 0000000..266875e
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/rotation/domain/interactor/RotationLockTileDataInteractorTest.kt
@@ -0,0 +1,227 @@
+/*
+ * 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.qs.tiles.impl.rotation.domain.interactor
+
+import android.Manifest
+import android.content.packageManager
+import android.content.pm.PackageManager
+import android.os.UserHandle
+import android.platform.test.annotations.EnabledOnRavenwood
+import android.testing.LeakCheck
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.camera.data.repository.fakeCameraAutoRotateRepository
+import com.android.systemui.camera.data.repository.fakeCameraSensorPrivacyRepository
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.whenever
+import com.android.systemui.utils.leaks.FakeBatteryController
+import com.android.systemui.utils.leaks.FakeRotationLockController
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.toCollection
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@EnabledOnRavenwood
+@RunWith(AndroidJUnit4::class)
+class RotationLockTileDataInteractorTest : SysuiTestCase() {
+    private val kosmos = Kosmos()
+    private val testScope = kosmos.testScope
+    private val batteryController = FakeBatteryController(LeakCheck())
+    private val rotationController = FakeRotationLockController(LeakCheck())
+    private val fakeCameraAutoRotateRepository = kosmos.fakeCameraAutoRotateRepository
+    private val fakeCameraSensorPrivacyRepository = kosmos.fakeCameraSensorPrivacyRepository
+    private val packageManager = kosmos.packageManager
+
+    private val testUser = UserHandle.of(1)
+    private lateinit var underTest: RotationLockTileDataInteractor
+
+    @Before
+    fun setup() {
+        whenever(packageManager.rotationResolverPackageName).thenReturn(TEST_PACKAGE_NAME)
+        whenever(
+                packageManager.checkPermission(
+                    eq(Manifest.permission.CAMERA),
+                    eq(TEST_PACKAGE_NAME)
+                )
+            )
+            .thenReturn(PackageManager.PERMISSION_GRANTED)
+
+        underTest =
+            RotationLockTileDataInteractor(
+                rotationController,
+                batteryController,
+                fakeCameraAutoRotateRepository,
+                fakeCameraSensorPrivacyRepository,
+                packageManager,
+                context.orCreateTestableResources
+                    .apply {
+                        addOverride(com.android.internal.R.bool.config_allowRotationResolver, true)
+                    }
+                    .resources
+            )
+    }
+
+    @Test
+    fun availability_isTrue() =
+        testScope.runTest {
+            val availability = underTest.availability(testUser).toCollection(mutableListOf())
+
+            assertThat(availability).hasSize(1)
+            assertThat(availability.last()).isTrue()
+        }
+
+    @Test
+    fun tileData_isRotationLockedMatchesRotationController() =
+        testScope.runTest {
+            val data by
+                collectLastValue(
+                    underTest.tileData(testUser, flowOf(DataUpdateTrigger.InitialRequest))
+                )
+
+            runCurrent()
+            assertThat(data!!.isRotationLocked).isEqualTo(false)
+
+            rotationController.setRotationLocked(true, CALLER)
+            runCurrent()
+            assertThat(data!!.isRotationLocked).isEqualTo(true)
+
+            rotationController.setRotationLocked(false, CALLER)
+            runCurrent()
+            assertThat(data!!.isRotationLocked).isEqualTo(false)
+        }
+
+    @Test
+    fun tileData_cameraRotationMatchesBatteryController() =
+        testScope.runTest {
+            setupControllersToEnableCameraRotation()
+            val data by
+                collectLastValue(
+                    underTest.tileData(testUser, flowOf(DataUpdateTrigger.InitialRequest))
+                )
+
+            runCurrent()
+            assertThat(data!!.isCameraRotationEnabled).isTrue()
+
+            batteryController.setPowerSaveMode(true)
+            runCurrent()
+            assertThat(data!!.isCameraRotationEnabled).isFalse()
+
+            batteryController.setPowerSaveMode(false)
+            runCurrent()
+            assertThat(data!!.isCameraRotationEnabled).isTrue()
+        }
+
+    @Test
+    fun tileData_cameraRotationMatchesSensorPrivacyRepository() =
+        testScope.runTest {
+            setupControllersToEnableCameraRotation()
+            val lastValue by
+                this.collectLastValue(
+                    underTest.tileData(testUser, flowOf(DataUpdateTrigger.InitialRequest))
+                )
+            runCurrent()
+            assertThat(lastValue!!.isCameraRotationEnabled).isTrue()
+
+            fakeCameraSensorPrivacyRepository.setEnabled(testUser, true)
+            runCurrent()
+            assertThat(lastValue!!.isCameraRotationEnabled).isFalse()
+
+            fakeCameraSensorPrivacyRepository.setEnabled(testUser, false)
+            runCurrent()
+            assertThat(lastValue!!.isCameraRotationEnabled).isTrue()
+        }
+
+    @Test
+    fun tileData_cameraRotationMatchesAutoRotateRepository() =
+        testScope.runTest {
+            setupControllersToEnableCameraRotation()
+
+            val lastValue by
+                collectLastValue(
+                    underTest.tileData(testUser, flowOf(DataUpdateTrigger.InitialRequest))
+                )
+            runCurrent()
+            assertThat(lastValue!!.isCameraRotationEnabled).isTrue()
+
+            fakeCameraAutoRotateRepository.setEnabled(testUser, false)
+            runCurrent()
+            assertThat(lastValue!!.isCameraRotationEnabled).isFalse()
+
+            fakeCameraAutoRotateRepository.setEnabled(testUser, true)
+            runCurrent()
+            assertThat(lastValue!!.isCameraRotationEnabled).isTrue()
+        }
+
+    @Test
+    fun tileData_matchesPackageManagerPermissionDenied() =
+        testScope.runTest {
+            whenever(
+                    packageManager.checkPermission(
+                        eq(Manifest.permission.CAMERA),
+                        eq(TEST_PACKAGE_NAME)
+                    )
+                )
+                .thenReturn(PackageManager.PERMISSION_DENIED)
+
+            val lastValue by
+                collectLastValue(
+                    underTest.tileData(testUser, flowOf(DataUpdateTrigger.InitialRequest))
+                )
+            runCurrent()
+            assertThat(lastValue!!.isCameraRotationEnabled).isEqualTo(false)
+        }
+
+    @Test
+    fun tileData_setConfigAllowRotationResolverToFalse_cameraRotationIsNotEnabled() =
+        testScope.runTest {
+            underTest.apply {
+                overrideResource(com.android.internal.R.bool.config_allowRotationResolver, false)
+            }
+            setupControllersToEnableCameraRotation()
+            val lastValue by
+                collectLastValue(
+                    underTest.tileData(testUser, flowOf(DataUpdateTrigger.InitialRequest))
+                )
+            runCurrent()
+
+            assertThat(lastValue!!.isCameraRotationEnabled).isEqualTo(false)
+        }
+
+    private fun setupControllersToEnableCameraRotation() {
+        rotationController.setRotationLocked(true, CALLER)
+        batteryController.setPowerSaveMode(false)
+        fakeCameraSensorPrivacyRepository.setEnabled(testUser, false)
+        fakeCameraAutoRotateRepository.setEnabled(testUser, true)
+    }
+
+    private companion object {
+        private const val CALLER = "RotationLockTileDataInteractorTest"
+        private const val TEST_PACKAGE_NAME = "com.test"
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/rotation/domain/interactor/RotationLockTileUserActionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/rotation/domain/interactor/RotationLockTileUserActionInteractorTest.kt
new file mode 100644
index 0000000..1653ce3
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/rotation/domain/interactor/RotationLockTileUserActionInteractorTest.kt
@@ -0,0 +1,96 @@
+/*
+ * 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.qs.tiles.impl.rotation.domain.interactor
+
+import android.platform.test.annotations.EnabledOnRavenwood
+import android.provider.Settings
+import android.testing.LeakCheck
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.qs.tiles.base.actions.FakeQSTileIntentUserInputHandler
+import com.android.systemui.qs.tiles.base.actions.QSTileIntentUserInputHandlerSubject
+import com.android.systemui.qs.tiles.base.interactor.QSTileInputTestKtx
+import com.android.systemui.qs.tiles.impl.rotation.domain.model.RotationLockTileModel
+import com.android.systemui.utils.leaks.FakeRotationLockController
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@EnabledOnRavenwood
+@RunWith(AndroidJUnit4::class)
+class RotationLockTileUserActionInteractorTest : SysuiTestCase() {
+    private val controller = FakeRotationLockController(LeakCheck())
+    private val inputHandler = FakeQSTileIntentUserInputHandler()
+
+    private val underTest =
+        RotationLockTileUserActionInteractor(
+            controller,
+            inputHandler,
+        )
+
+    @Test
+    fun handleClickWhenEnabled() = runTest {
+        val wasEnabled = true
+        controller.setRotationLocked(wasEnabled, null)
+
+        underTest.handleInput(QSTileInputTestKtx.click(RotationLockTileModel(wasEnabled, false)))
+
+        assertThat(controller.isRotationLocked).isEqualTo(!wasEnabled)
+    }
+
+    @Test
+    fun handleClickWhenDisabled() = runTest {
+        val wasEnabled = false
+        controller.setRotationLocked(wasEnabled, null)
+
+        underTest.handleInput(QSTileInputTestKtx.click(RotationLockTileModel(wasEnabled, false)))
+
+        assertThat(controller.isRotationLocked).isEqualTo(!wasEnabled)
+    }
+
+    @Test
+    fun handleLongClickWhenDisabled() = runTest {
+        val enabled = false
+
+        underTest.handleInput(
+            QSTileInputTestKtx.longClick(
+                RotationLockTileModel(
+                    enabled,
+                    false,
+                )
+            )
+        )
+
+        QSTileIntentUserInputHandlerSubject.assertThat(inputHandler).handledOneIntentInput {
+            assertThat(it.intent.action).isEqualTo(Settings.ACTION_AUTO_ROTATE_SETTINGS)
+        }
+    }
+
+    @Test
+    fun handleLongClickWhenEnabled() = runTest {
+        val enabled = true
+
+        underTest.handleInput(QSTileInputTestKtx.longClick(RotationLockTileModel(enabled, false)))
+
+        QSTileIntentUserInputHandlerSubject.assertThat(inputHandler).handledOneIntentInput {
+            assertThat(it.intent.action).isEqualTo(Settings.ACTION_AUTO_ROTATE_SETTINGS)
+        }
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/rotation/ui/mapper/RotationLockTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/rotation/ui/mapper/RotationLockTileMapperTest.kt
new file mode 100644
index 0000000..60c69f4
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/rotation/ui/mapper/RotationLockTileMapperTest.kt
@@ -0,0 +1,190 @@
+/*
+ * 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.qs.tiles.impl.rotation.ui.mapper
+
+import android.graphics.drawable.TestStubDrawable
+import android.widget.Switch
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.qs.tiles.impl.custom.QSTileStateSubject
+import com.android.systemui.qs.tiles.impl.rotation.domain.model.RotationLockTileModel
+import com.android.systemui.qs.tiles.impl.rotation.qsRotationLockTileConfig
+import com.android.systemui.qs.tiles.viewmodel.QSTileState
+import com.android.systemui.res.R
+import com.android.systemui.statusbar.policy.DevicePostureController
+import com.android.systemui.statusbar.policy.devicePostureController
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class RotationLockTileMapperTest : SysuiTestCase() {
+    private val kosmos = Kosmos()
+    private val rotationLockTileConfig = kosmos.qsRotationLockTileConfig
+    private val devicePostureController = kosmos.devicePostureController
+
+    private lateinit var mapper: RotationLockTileMapper
+
+    @Before
+    fun setup() {
+        whenever(devicePostureController.devicePosture)
+            .thenReturn(DevicePostureController.DEVICE_POSTURE_CLOSED)
+
+        mapper =
+            RotationLockTileMapper(
+                context.orCreateTestableResources
+                    .apply {
+                        addOverride(R.drawable.qs_auto_rotate_icon_off, TestStubDrawable())
+                        addOverride(R.drawable.qs_auto_rotate_icon_on, TestStubDrawable())
+                        addOverride(com.android.internal.R.bool.config_allowRotationResolver, true)
+                        addOverride(
+                            com.android.internal.R.array.config_foldedDeviceStates,
+                            intArrayOf() // empty array <=> device is not foldable
+                        )
+                    }
+                    .resources,
+                context.theme,
+                devicePostureController
+            )
+    }
+
+    @Test
+    fun rotationNotLocked_cameraRotationDisabled() {
+        val inputModel = RotationLockTileModel(false, false)
+
+        val outputState = mapper.map(rotationLockTileConfig, inputModel)
+
+        val expectedState =
+            createRotationLockTileState(
+                QSTileState.ActivationState.ACTIVE,
+                EMPTY_SECONDARY_STRING,
+                R.drawable.qs_auto_rotate_icon_on
+            )
+        QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+    }
+
+    @Test
+    fun rotationNotLocked_cameraRotationEnabled() {
+        val inputModel = RotationLockTileModel(false, true)
+
+        val outputState = mapper.map(rotationLockTileConfig, inputModel)
+
+        val expectedState =
+            createRotationLockTileState(
+                QSTileState.ActivationState.ACTIVE,
+                context.getString(R.string.rotation_lock_camera_rotation_on),
+                R.drawable.qs_auto_rotate_icon_on
+            )
+        QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+    }
+
+    @Test
+    fun rotationLocked_cameraRotationNotEnabled() {
+        val inputModel = RotationLockTileModel(true, false)
+
+        val outputState = mapper.map(rotationLockTileConfig, inputModel)
+
+        val expectedState =
+            createRotationLockTileState(
+                QSTileState.ActivationState.INACTIVE,
+                EMPTY_SECONDARY_STRING,
+                R.drawable.qs_auto_rotate_icon_off
+            )
+        QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+    }
+
+    @Test
+    fun deviceFoldableAndClosed_secondaryLabelIsFoldableSpecific() {
+        setDeviceFoldable()
+        val inputModel = RotationLockTileModel(false, false)
+
+        val outputState = mapper.map(rotationLockTileConfig, inputModel)
+
+        val expectedSecondaryLabelEnding =
+            context.getString(R.string.quick_settings_rotation_posture_folded)
+        assertThat(
+                context.resources.getIntArray(
+                    com.android.internal.R.array.config_foldedDeviceStates
+                )
+            )
+            .isNotEmpty()
+        val actualSecondaryLabel = outputState.secondaryLabel
+        assertThat(actualSecondaryLabel).isNotNull()
+        assertThat(actualSecondaryLabel!!.endsWith(expectedSecondaryLabelEnding)).isTrue()
+    }
+
+    @Test
+    fun deviceFoldableAndNotClosed_secondaryLabelIsFoldableSpecific() {
+        setDeviceFoldable()
+        whenever(devicePostureController.devicePosture)
+            .thenReturn(DevicePostureController.DEVICE_POSTURE_OPENED)
+        val inputModel = RotationLockTileModel(false, false)
+
+        val outputState = mapper.map(rotationLockTileConfig, inputModel)
+
+        val expectedSecondaryLabelEnding =
+            context.getString(R.string.quick_settings_rotation_posture_unfolded)
+        assertThat(
+                context.orCreateTestableResources.resources.getIntArray(
+                    com.android.internal.R.array.config_foldedDeviceStates
+                )
+            )
+            .isNotEmpty()
+        val actualSecondaryLabel = outputState.secondaryLabel
+        assertThat(actualSecondaryLabel).isNotNull()
+        assertThat(actualSecondaryLabel!!.endsWith(expectedSecondaryLabelEnding)).isTrue()
+    }
+
+    private fun setDeviceFoldable() {
+        mapper.apply {
+            overrideResource(
+                com.android.internal.R.array.config_foldedDeviceStates,
+                intArrayOf(1, 2, 3)
+            )
+        }
+    }
+
+    private fun createRotationLockTileState(
+        activationState: QSTileState.ActivationState,
+        secondaryLabel: String,
+        iconRes: Int
+    ): QSTileState {
+        val label = context.getString(R.string.quick_settings_rotation_unlocked_label)
+        return QSTileState(
+            { Icon.Loaded(context.getDrawable(iconRes)!!, null) },
+            label,
+            activationState,
+            secondaryLabel,
+            setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK),
+            context.getString(R.string.accessibility_quick_settings_rotation),
+            secondaryLabel,
+            QSTileState.SideViewIcon.None,
+            QSTileState.EnabledState.ENABLED,
+            Switch::class.qualifiedName
+        )
+    }
+
+    private companion object {
+        private const val EMPTY_SECONDARY_STRING = ""
+    }
+}
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 346bdfc..b713417 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -3254,6 +3254,12 @@
     <!-- Text for education page content description for unfolded animation. [CHAR_LIMIT=NONE] -->
     <string name="rear_display_accessibility_unfolded_animation">Foldable device being flipped around</string>
 
+    <!-- QuickSettings: Additional label for the auto-rotation quicksettings tile indicating that the setting corresponds to the folded posture for a foldable device [CHAR LIMIT=32] -->
+    <string name="quick_settings_rotation_posture_folded">folded</string>
+    <!-- QuickSettings: Additional label for the auto-rotation quicksettings tile indicating that the setting corresponds to the unfolded posture for a foldable device [CHAR LIMIT=32] -->
+    <string name="quick_settings_rotation_posture_unfolded">unfolded</string>
+    <!-- QuickSettings: template for rotation tile foldable secondary label [CHAR LIMIT=64] !-->
+    <string name="rotation_tile_with_posture_secondary_label_template">%1$s / %2$s</string>
     <!-- Title for notification of low stylus battery with percentage. "percentage" is
         the value of the battery capacity remaining [CHAR LIMIT=none]-->
     <string name="stylus_battery_low_percentage"><xliff:g id="percentage" example="16%">%s</xliff:g> battery remaining</string>
diff --git a/packages/SystemUI/src/com/android/systemui/camera/CameraRotationModule.kt b/packages/SystemUI/src/com/android/systemui/camera/CameraRotationModule.kt
new file mode 100644
index 0000000..f123828
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/camera/CameraRotationModule.kt
@@ -0,0 +1,33 @@
+/*
+ * 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.camera
+
+import com.android.systemui.camera.data.repository.CameraAutoRotateRepository
+import com.android.systemui.camera.data.repository.CameraAutoRotateRepositoryImpl
+import com.android.systemui.camera.data.repository.CameraSensorPrivacyRepository
+import com.android.systemui.camera.data.repository.CameraSensorPrivacyRepositoryImpl
+import dagger.Binds
+import dagger.Module
+
+/** Module for repositories that provide data regarding camera rotation state. */
+@Module
+interface CameraRotationModule {
+
+    @Binds
+    fun bindsPrivacyRepoImpl(impl: CameraSensorPrivacyRepositoryImpl): CameraSensorPrivacyRepository
+    @Binds fun bindsRotateRepoImpl(impl: CameraAutoRotateRepositoryImpl): CameraAutoRotateRepository
+}
diff --git a/packages/SystemUI/src/com/android/systemui/camera/data/repository/CameraAutoRotateRepository.kt b/packages/SystemUI/src/com/android/systemui/camera/data/repository/CameraAutoRotateRepository.kt
new file mode 100644
index 0000000..023fd28
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/camera/data/repository/CameraAutoRotateRepository.kt
@@ -0,0 +1,70 @@
+/*
+ * 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.camera.data.repository
+
+import android.os.UserHandle
+import android.provider.Settings
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.util.settings.SecureSettings
+import com.android.systemui.util.settings.SettingsProxyExt.observerFlow
+import javax.inject.Inject
+import kotlin.coroutines.CoroutineContext
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onStart
+import kotlinx.coroutines.flow.stateIn
+
+interface CameraAutoRotateRepository {
+    /** @return true if camera auto rotate setting is enabled */
+    fun isCameraAutoRotateSettingEnabled(userHandle: UserHandle): StateFlow<Boolean>
+}
+
+@SysUISingleton
+class CameraAutoRotateRepositoryImpl
+@Inject
+constructor(
+    private val secureSettings: SecureSettings,
+    @Background private val bgCoroutineContext: CoroutineContext,
+    @Application private val applicationScope: CoroutineScope,
+) : CameraAutoRotateRepository {
+    private val userMap = mutableMapOf<Int, StateFlow<Boolean>>()
+
+    override fun isCameraAutoRotateSettingEnabled(userHandle: UserHandle): StateFlow<Boolean> {
+        return userMap.getOrPut(userHandle.identifier) {
+            secureSettings
+                .observerFlow(userHandle.identifier, Settings.Secure.CAMERA_AUTOROTATE)
+                .map { isAutoRotateSettingEnabled(userHandle.identifier) }
+                .onStart { emit(isAutoRotateSettingEnabled(userHandle.identifier)) }
+                .flowOn(bgCoroutineContext)
+                .stateIn(applicationScope, SharingStarted.WhileSubscribed(), false)
+        }
+    }
+
+    private fun isAutoRotateSettingEnabled(userId: Int) =
+        secureSettings.getIntForUser(SETTING_NAME, DISABLED, userId) == ENABLED
+
+    private companion object {
+        const val SETTING_NAME = Settings.Secure.CAMERA_AUTOROTATE
+        const val DISABLED = 0
+        const val ENABLED = 1
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/camera/data/repository/CameraSensorPrivacyRepository.kt b/packages/SystemUI/src/com/android/systemui/camera/data/repository/CameraSensorPrivacyRepository.kt
new file mode 100644
index 0000000..7816a14
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/camera/data/repository/CameraSensorPrivacyRepository.kt
@@ -0,0 +1,76 @@
+/*
+ * 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.camera.data.repository
+
+import android.hardware.SensorPrivacyManager
+import android.hardware.SensorPrivacyManager.Sensors.CAMERA
+import android.os.UserHandle
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import javax.inject.Inject
+import kotlin.coroutines.CoroutineContext
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.onStart
+import kotlinx.coroutines.flow.stateIn
+
+interface CameraSensorPrivacyRepository {
+    /** Tracks whether camera sensor privacy is enabled. */
+    fun isEnabled(userHandle: UserHandle): StateFlow<Boolean>
+}
+
+@SysUISingleton
+class CameraSensorPrivacyRepositoryImpl
+@Inject
+constructor(
+    @Background private val bgCoroutineContext: CoroutineContext,
+    @Application private val scope: CoroutineScope,
+    private val privacyManager: SensorPrivacyManager,
+) : CameraSensorPrivacyRepository {
+    private val userMap = mutableMapOf<Int, StateFlow<Boolean>>()
+
+    /** Whether camera sensor privacy is enabled */
+    override fun isEnabled(userHandle: UserHandle): StateFlow<Boolean> =
+        userMap.getOrPut(userHandle.identifier) {
+            privacyManager
+                .isEnabled(userHandle)
+                .flowOn(bgCoroutineContext)
+                .stateIn(scope, SharingStarted.WhileSubscribed(), false)
+        }
+}
+
+fun SensorPrivacyManager.isEnabled(userHandle: UserHandle): Flow<Boolean> {
+    return conflatedCallbackFlow {
+            val privacyCallback =
+                SensorPrivacyManager.OnSensorPrivacyChangedListener { sensor, enabled ->
+                    if (sensor == CAMERA) {
+                        trySend(enabled)
+                    }
+                }
+            addSensorPrivacyListener(CAMERA, userHandle.identifier, privacyCallback)
+            awaitClose { removeSensorPrivacyListener(privacyCallback) }
+        }
+        .onStart { emit(isSensorPrivacyEnabled(SensorPrivacyManager.TOGGLE_TYPE_SOFTWARE, CAMERA)) }
+        .distinctUntilChanged()
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
index f7bc5cdc..a4011fd 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
@@ -43,6 +43,7 @@
 import com.android.systemui.recents.Recents;
 import com.android.systemui.recents.RecentsImplementation;
 import com.android.systemui.rotationlock.RotationLockModule;
+import com.android.systemui.rotationlock.RotationLockNewModule;
 import com.android.systemui.scene.SceneContainerFrameworkModule;
 import com.android.systemui.screenshot.ReferenceScreenshotModule;
 import com.android.systemui.settings.MultiUserUtilsModule;
@@ -110,6 +111,7 @@
         RearDisplayModule.class,
         ReferenceScreenshotModule.class,
         RotationLockModule.class,
+        RotationLockNewModule.class,
         ScreenDecorationsModule.class,
         SystemActionsModule.class,
         ShadeModule.class,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/rotation/domain/interactor/RotationLockTileDataInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/rotation/domain/interactor/RotationLockTileDataInteractor.kt
new file mode 100644
index 0000000..736e1a5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/rotation/domain/interactor/RotationLockTileDataInteractor.kt
@@ -0,0 +1,94 @@
+/*
+ * 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.qs.tiles.impl.rotation.domain.interactor
+
+import android.Manifest
+import android.content.pm.PackageManager
+import android.content.res.Resources
+import android.os.UserHandle
+import com.android.systemui.camera.data.repository.CameraAutoRotateRepository
+import com.android.systemui.camera.data.repository.CameraSensorPrivacyRepository
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger
+import com.android.systemui.qs.tiles.base.interactor.QSTileDataInteractor
+import com.android.systemui.qs.tiles.impl.rotation.domain.model.RotationLockTileModel
+import com.android.systemui.statusbar.policy.BatteryController
+import com.android.systemui.statusbar.policy.RotationLockController
+import com.android.systemui.util.kotlin.isBatteryPowerSaveEnabled
+import com.android.systemui.util.kotlin.isRotationLockEnabled
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.flowOf
+
+/** Observes rotation lock state changes providing the [RotationLockTileModel]. */
+class RotationLockTileDataInteractor
+@Inject
+constructor(
+    private val rotationLockController: RotationLockController,
+    private val batteryController: BatteryController,
+    private val cameraAutoRotateRepository: CameraAutoRotateRepository,
+    private val cameraSensorPrivacyRepository: CameraSensorPrivacyRepository,
+    private val packageManager: PackageManager,
+    @Main private val resources: Resources,
+) : QSTileDataInteractor<RotationLockTileModel> {
+
+    override fun tileData(
+        user: UserHandle,
+        triggers: Flow<DataUpdateTrigger>
+    ): Flow<RotationLockTileModel> =
+        combine(
+            rotationLockController.isRotationLockEnabled(),
+            cameraSensorPrivacyRepository.isEnabled(user),
+            batteryController.isBatteryPowerSaveEnabled(),
+            cameraAutoRotateRepository.isCameraAutoRotateSettingEnabled(user)
+        ) {
+            isRotationLockEnabled,
+            isCamPrivacySensorEnabled,
+            isBatteryPowerSaveEnabled,
+            isCameraAutoRotateEnabled,
+            ->
+            RotationLockTileModel(
+                isRotationLockEnabled,
+                isCameraRotationEnabled(
+                    isBatteryPowerSaveEnabled,
+                    isCamPrivacySensorEnabled,
+                    isCameraAutoRotateEnabled
+                ),
+            )
+        }
+
+    override fun availability(user: UserHandle): Flow<Boolean> = flowOf(true)
+
+    private fun hasSufficientPermission(): Boolean {
+        val rotationPackage: String = packageManager.rotationResolverPackageName
+        return rotationPackage != null &&
+            packageManager.checkPermission(Manifest.permission.CAMERA, rotationPackage) ==
+                PackageManager.PERMISSION_GRANTED
+    }
+
+    private fun isCameraRotationEnabled(
+        isBatteryPowerSaverModeOn: Boolean,
+        isCameraSensorPrivacyEnabled: Boolean,
+        isCameraAutoRotateEnabled: Boolean
+    ): Boolean =
+        resources.getBoolean(com.android.internal.R.bool.config_allowRotationResolver) &&
+            !isBatteryPowerSaverModeOn &&
+            !isCameraSensorPrivacyEnabled &&
+            hasSufficientPermission() &&
+            isCameraAutoRotateEnabled
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/rotation/domain/interactor/RotationLockTileUserActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/rotation/domain/interactor/RotationLockTileUserActionInteractor.kt
new file mode 100644
index 0000000..8530926
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/rotation/domain/interactor/RotationLockTileUserActionInteractor.kt
@@ -0,0 +1,56 @@
+/*
+ * 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.qs.tiles.impl.rotation.domain.interactor
+
+import android.content.Intent
+import android.provider.Settings
+import com.android.systemui.qs.tiles.base.actions.QSTileIntentUserInputHandler
+import com.android.systemui.qs.tiles.base.interactor.QSTileInput
+import com.android.systemui.qs.tiles.base.interactor.QSTileUserActionInteractor
+import com.android.systemui.qs.tiles.impl.rotation.domain.model.RotationLockTileModel
+import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction
+import com.android.systemui.statusbar.policy.RotationLockController
+import javax.inject.Inject
+
+/** Handles rotation lock tile clicks. */
+class RotationLockTileUserActionInteractor
+@Inject
+constructor(
+    private val controller: RotationLockController,
+    private val qsTileIntentUserActionHandler: QSTileIntentUserInputHandler,
+) : QSTileUserActionInteractor<RotationLockTileModel> {
+
+    override suspend fun handleInput(input: QSTileInput<RotationLockTileModel>) {
+        with(input) {
+            when (action) {
+                is QSTileUserAction.Click -> {
+                    controller.setRotationLocked(!data.isRotationLocked, CALLER)
+                }
+                is QSTileUserAction.LongClick -> {
+                    qsTileIntentUserActionHandler.handle(
+                        action.view,
+                        Intent(Settings.ACTION_AUTO_ROTATE_SETTINGS)
+                    )
+                }
+            }
+        }
+    }
+
+    companion object {
+        private const val CALLER = "QSTileUserActionInteractor#handleInput"
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/rotation/domain/model/RotationLockTileModel.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/rotation/domain/model/RotationLockTileModel.kt
new file mode 100644
index 0000000..32e6cb8c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/rotation/domain/model/RotationLockTileModel.kt
@@ -0,0 +1,23 @@
+/*
+ * 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.qs.tiles.impl.rotation.domain.model
+
+/** Model for rotation lock tile */
+class RotationLockTileModel(
+    val isRotationLocked: Boolean,
+    val isCameraRotationEnabled: Boolean,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/rotation/ui/mapper/RotationLockTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/rotation/ui/mapper/RotationLockTileMapper.kt
new file mode 100644
index 0000000..070cdef
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/rotation/ui/mapper/RotationLockTileMapper.kt
@@ -0,0 +1,107 @@
+/*
+ * 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.qs.tiles.impl.rotation.ui.mapper
+
+import android.content.res.Resources
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper
+import com.android.systemui.qs.tiles.impl.rotation.domain.model.RotationLockTileModel
+import com.android.systemui.qs.tiles.viewmodel.QSTileConfig
+import com.android.systemui.qs.tiles.viewmodel.QSTileState
+import com.android.systemui.res.R
+import com.android.systemui.statusbar.policy.DevicePostureController
+import javax.inject.Inject
+
+/** Maps [RotationLockTileModel] to [QSTileState]. */
+class RotationLockTileMapper
+@Inject
+constructor(
+    @Main private val resources: Resources,
+    private val theme: Resources.Theme,
+    private val devicePostureController: DevicePostureController
+) : QSTileDataToStateMapper<RotationLockTileModel> {
+    override fun map(config: QSTileConfig, data: RotationLockTileModel): QSTileState =
+        QSTileState.build(resources, theme, config.uiConfig) {
+            this.label = resources.getString(R.string.quick_settings_rotation_unlocked_label)
+            this.contentDescription =
+                resources.getString(R.string.accessibility_quick_settings_rotation)
+
+            if (data.isRotationLocked) {
+                activationState = QSTileState.ActivationState.INACTIVE
+                this.secondaryLabel = EMPTY_SECONDARY_STRING
+                this.icon = {
+                    Icon.Loaded(
+                        resources.getDrawable(R.drawable.qs_auto_rotate_icon_off, theme),
+                        contentDescription = null
+                    )
+                }
+            } else {
+                activationState = QSTileState.ActivationState.ACTIVE
+                this.secondaryLabel =
+                    if (data.isCameraRotationEnabled) {
+                        resources.getString(R.string.rotation_lock_camera_rotation_on)
+                    } else {
+                        EMPTY_SECONDARY_STRING
+                    }
+                this.icon = {
+                    Icon.Loaded(
+                        resources.getDrawable(R.drawable.qs_auto_rotate_icon_on, theme),
+                        contentDescription = null
+                    )
+                }
+            }
+            if (isDeviceFoldable()) {
+                this.secondaryLabel = getSecondaryLabelWithPosture(this.activationState)
+            }
+            this.stateDescription = this.secondaryLabel
+            this.sideViewIcon = QSTileState.SideViewIcon.None
+            supportedActions =
+                setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK)
+        }
+
+    private fun isDeviceFoldable(): Boolean {
+        val intArray = resources.getIntArray(com.android.internal.R.array.config_foldedDeviceStates)
+        return intArray.isNotEmpty()
+    }
+
+    private fun getSecondaryLabelWithPosture(activationState: QSTileState.ActivationState): String {
+        val stateNames = resources.getStringArray(R.array.tile_states_rotation)
+        val stateName =
+            stateNames[
+                if (activationState == QSTileState.ActivationState.ACTIVE) ON_INDEX else OFF_INDEX]
+        val posture =
+            if (
+                devicePostureController.devicePosture ==
+                    DevicePostureController.DEVICE_POSTURE_CLOSED
+            )
+                resources.getString(R.string.quick_settings_rotation_posture_folded)
+            else resources.getString(R.string.quick_settings_rotation_posture_unfolded)
+
+        return resources.getString(
+            R.string.rotation_tile_with_posture_secondary_label_template,
+            stateName,
+            posture
+        )
+    }
+
+    private companion object {
+        const val EMPTY_SECONDARY_STRING = ""
+        const val OFF_INDEX = 1
+        const val ON_INDEX = 2
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/rotationlock/RotationLockNewModule.kt b/packages/SystemUI/src/com/android/systemui/rotationlock/RotationLockNewModule.kt
new file mode 100644
index 0000000..eb64dd6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/rotationlock/RotationLockNewModule.kt
@@ -0,0 +1,73 @@
+/*
+ * 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.rotationlock
+
+import com.android.systemui.camera.CameraRotationModule
+import com.android.systemui.qs.QsEventLogger
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.tiles.base.viewmodel.QSTileViewModelFactory
+import com.android.systemui.qs.tiles.impl.rotation.domain.interactor.RotationLockTileDataInteractor
+import com.android.systemui.qs.tiles.impl.rotation.domain.interactor.RotationLockTileUserActionInteractor
+import com.android.systemui.qs.tiles.impl.rotation.domain.model.RotationLockTileModel
+import com.android.systemui.qs.tiles.impl.rotation.ui.mapper.RotationLockTileMapper
+import com.android.systemui.qs.tiles.viewmodel.QSTileConfig
+import com.android.systemui.qs.tiles.viewmodel.QSTileUIConfig
+import com.android.systemui.qs.tiles.viewmodel.QSTileViewModel
+import com.android.systemui.res.R
+import dagger.Module
+import dagger.Provides
+import dagger.multibindings.IntoMap
+import dagger.multibindings.StringKey
+
+@Module(includes = [CameraRotationModule::class])
+interface RotationLockNewModule {
+    companion object {
+        private const val ROTATION_TILE_SPEC = "rotation"
+
+        /** Inject rotation tile config */
+        @Provides
+        @IntoMap
+        @StringKey(ROTATION_TILE_SPEC)
+        fun provideRotationTileConfig(uiEventLogger: QsEventLogger): QSTileConfig =
+            QSTileConfig(
+                tileSpec = TileSpec.create(ROTATION_TILE_SPEC),
+                uiConfig =
+                    QSTileUIConfig.Resource(
+                        iconRes = R.drawable.qs_auto_rotate_icon_off,
+                        labelRes = R.string.quick_settings_rotation_unlocked_label,
+                    ),
+                instanceId = uiEventLogger.getNewInstanceId(),
+            )
+
+        /** Inject Rotation tile into tileViewModelMap in QSModule */
+        @Provides
+        @IntoMap
+        @StringKey(ROTATION_TILE_SPEC)
+        fun provideRotationTileViewModel(
+            factory: QSTileViewModelFactory.Static<RotationLockTileModel>,
+            mapper: RotationLockTileMapper,
+            stateInteractor: RotationLockTileDataInteractor,
+            userActionInteractor: RotationLockTileUserActionInteractor
+        ): QSTileViewModel =
+            factory.create(
+                TileSpec.create(ROTATION_TILE_SPEC),
+                userActionInteractor,
+                stateInteractor,
+                mapper,
+            )
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/BatteryControllerExt.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/BatteryControllerExt.kt
new file mode 100644
index 0000000..0128eb7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/BatteryControllerExt.kt
@@ -0,0 +1,37 @@
+/*
+ * 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.util.kotlin
+
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.statusbar.policy.BatteryController
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.onStart
+
+fun BatteryController.isBatteryPowerSaveEnabled(): Flow<Boolean> {
+    return conflatedCallbackFlow {
+            val batteryCallback =
+                object : BatteryController.BatteryStateChangeCallback {
+                    override fun onPowerSaveChanged(isPowerSave: Boolean) {
+                        trySend(isPowerSave)
+                    }
+                }
+            addCallback(batteryCallback)
+            awaitClose { removeCallback(batteryCallback) }
+        }
+        .onStart { emit(isPowerSave) }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/RotationLockControllerExt.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/RotationLockControllerExt.kt
new file mode 100644
index 0000000..22cc8dd
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/RotationLockControllerExt.kt
@@ -0,0 +1,35 @@
+/*
+ * 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.util.kotlin
+
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.statusbar.policy.RotationLockController
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.onStart
+
+fun RotationLockController.isRotationLockEnabled(): Flow<Boolean> {
+    return conflatedCallbackFlow {
+            val rotationLockCallback =
+                RotationLockController.RotationLockControllerCallback { rotationLocked, _ ->
+                    trySend(rotationLocked)
+                }
+            addCallback(rotationLockCallback)
+            awaitClose { removeCallback(rotationLockCallback) }
+        }
+        .onStart { emit(isRotationLocked) }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/camera/data/repository/FakeCameraAutoRotateRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/camera/data/repository/FakeCameraAutoRotateRepository.kt
new file mode 100644
index 0000000..b8284ac
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/camera/data/repository/FakeCameraAutoRotateRepository.kt
@@ -0,0 +1,37 @@
+/*
+ * 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.camera.data.repository
+
+import android.os.UserHandle
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+
+class FakeCameraAutoRotateRepository : CameraAutoRotateRepository {
+    private val userMap = mutableMapOf<Int, MutableStateFlow<Boolean>>()
+
+    /** Send a Unit signal when value changes */
+    override fun isCameraAutoRotateSettingEnabled(userHandle: UserHandle): StateFlow<Boolean> =
+        getFlow(userHandle.identifier)
+
+    fun setEnabled(userHandle: UserHandle, enabled: Boolean) {
+        getFlow(userHandle.identifier).value = enabled
+    }
+
+    /** initializes the flow if already not */
+    private fun getFlow(userId: Int): MutableStateFlow<Boolean> =
+        userMap.getOrPut(userId) { MutableStateFlow(false) }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/camera/data/repository/FakeCameraAutoRotateRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/camera/data/repository/FakeCameraAutoRotateRepositoryKosmos.kt
new file mode 100644
index 0000000..615c596
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/camera/data/repository/FakeCameraAutoRotateRepositoryKosmos.kt
@@ -0,0 +1,22 @@
+/*
+ * 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.camera.data.repository
+
+import com.android.systemui.kosmos.Kosmos
+
+val Kosmos.fakeCameraAutoRotateRepository: FakeCameraAutoRotateRepository by
+    Kosmos.Fixture { FakeCameraAutoRotateRepository() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/camera/data/repository/FakeCameraSensorPrivacyRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/camera/data/repository/FakeCameraSensorPrivacyRepository.kt
new file mode 100644
index 0000000..994e9b2
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/camera/data/repository/FakeCameraSensorPrivacyRepository.kt
@@ -0,0 +1,36 @@
+/*
+ * 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.camera.data.repository
+
+import android.os.UserHandle
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+
+class FakeCameraSensorPrivacyRepository : CameraSensorPrivacyRepository {
+
+    private val userMap = mutableMapOf<Int, MutableStateFlow<Boolean>>()
+    override fun isEnabled(userHandle: UserHandle): StateFlow<Boolean> =
+        getFlow(userHandle.identifier)
+
+    fun setEnabled(userHandle: UserHandle, enabled: Boolean) {
+        getFlow(userHandle.identifier).value = enabled
+    }
+
+    /** initializes the flow if already not */
+    private fun getFlow(userId: Int): MutableStateFlow<Boolean> =
+        userMap.getOrPut(userId) { MutableStateFlow(false) }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/camera/data/repository/FakeCameraSensorPrivacyRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/camera/data/repository/FakeCameraSensorPrivacyRepositoryKosmos.kt
new file mode 100644
index 0000000..c7e704c
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/camera/data/repository/FakeCameraSensorPrivacyRepositoryKosmos.kt
@@ -0,0 +1,22 @@
+/*
+ * 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.camera.data.repository
+
+import com.android.systemui.kosmos.Kosmos
+
+val Kosmos.fakeCameraSensorPrivacyRepository: FakeCameraSensorPrivacyRepository by
+    Kosmos.Fixture { FakeCameraSensorPrivacyRepository() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/rotation/RotationLockTileKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/rotation/RotationLockTileKosmos.kt
new file mode 100644
index 0000000..ecf8ce5
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/rotation/RotationLockTileKosmos.kt
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.rotation
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.qs.qsEventLogger
+import com.android.systemui.rotationlock.RotationLockNewModule
+
+val Kosmos.qsRotationLockTileConfig by
+    Kosmos.Fixture { RotationLockNewModule.provideRotationTileConfig(qsEventLogger) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/DevicePostureControllerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/DevicePostureControllerKosmos.kt
new file mode 100644
index 0000000..89eaf15
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/DevicePostureControllerKosmos.kt
@@ -0,0 +1,22 @@
+/*
+ * 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.statusbar.policy
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.util.mockito.mock
+
+val Kosmos.devicePostureController by Kosmos.Fixture { mock<DevicePostureController>() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeRotationLockController.java b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeRotationLockController.java
index be57658..4aa85a7 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeRotationLockController.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeRotationLockController.java
@@ -19,13 +19,29 @@
 import com.android.systemui.statusbar.policy.RotationLockController;
 import com.android.systemui.statusbar.policy.RotationLockController.RotationLockControllerCallback;
 
+import java.util.ArrayList;
+import java.util.List;
+
 public class FakeRotationLockController extends BaseLeakChecker<RotationLockControllerCallback>
         implements RotationLockController {
+    private boolean mIsLocked = false;
+    private final List<RotationLockControllerCallback> mCallbacks = new ArrayList<>();
     public FakeRotationLockController(LeakCheck test) {
         super(test, "rotation");
     }
 
     @Override
+    public void addCallback(RotationLockControllerCallback listener) {
+        mCallbacks.add(listener);
+        listener.onRotationLockStateChanged(mIsLocked, isRotationLockAffordanceVisible());
+    }
+
+    @Override
+    public void removeCallback(RotationLockControllerCallback listener) {
+        mCallbacks.remove(listener);
+    }
+
+    @Override
     public void setListening(boolean listening) {
 
     }
@@ -42,12 +58,15 @@
 
     @Override
     public boolean isRotationLocked() {
-        return false;
+        return mIsLocked;
     }
 
     @Override
     public void setRotationLocked(boolean locked, String caller) {
-
+        mIsLocked = locked;
+        for (RotationLockControllerCallback callback : mCallbacks) {
+            callback.onRotationLockStateChanged(locked, isRotationLockAffordanceVisible());
+        }
     }
 
     @Override