Merge "Auto Add/Remove a11y tiles when the ACCESSIBILITY_QS_TILES updates" into main
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/AccessibilityQsShortcutsRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/AccessibilityQsShortcutsRepositoryImplTest.kt
new file mode 100644
index 0000000..9287edf
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/AccessibilityQsShortcutsRepositoryImplTest.kt
@@ -0,0 +1,82 @@
+/*
+ * 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.accessibility.data.repository
+
+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.collectLastValue
+import com.android.systemui.util.settings.FakeSettings
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class AccessibilityQsShortcutsRepositoryImplTest : SysuiTestCase() {
+    private val testDispatcher = StandardTestDispatcher()
+    private val testScope = TestScope(testDispatcher)
+    private val secureSettings = FakeSettings()
+
+    private val userA11yQsShortcutsRepositoryFactory =
+        object : UserA11yQsShortcutsRepository.Factory {
+            override fun create(userId: Int): UserA11yQsShortcutsRepository {
+                return UserA11yQsShortcutsRepository(
+                    userId,
+                    secureSettings,
+                    testScope.backgroundScope,
+                    testDispatcher,
+                )
+            }
+        }
+
+    private val underTest =
+        AccessibilityQsShortcutsRepositoryImpl(userA11yQsShortcutsRepositoryFactory)
+
+    @Test
+    fun a11yQsShortcutTargetsForCorrectUsers() =
+        testScope.runTest {
+            val user0 = 0
+            val targetsForUser0 = setOf("a", "b", "c")
+            val user1 = 1
+            val targetsForUser1 = setOf("A")
+            val targetsFromUser0 by collectLastValue(underTest.a11yQsShortcutTargets(user0))
+            val targetsFromUser1 by collectLastValue(underTest.a11yQsShortcutTargets(user1))
+
+            storeA11yQsShortcutTargetsForUser(targetsForUser0, user0)
+            storeA11yQsShortcutTargetsForUser(targetsForUser1, user1)
+
+            assertThat(targetsFromUser0).isEqualTo(targetsForUser0)
+            assertThat(targetsFromUser1).isEqualTo(targetsForUser1)
+        }
+
+    private fun storeA11yQsShortcutTargetsForUser(a11yQsTargets: Set<String>, forUser: Int) {
+        secureSettings.putStringForUser(
+            SETTING_NAME,
+            a11yQsTargets.joinToString(separator = ":"),
+            forUser
+        )
+    }
+
+    companion object {
+        private const val SETTING_NAME = Settings.Secure.ACCESSIBILITY_QS_TARGETS
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/UserA11yQsShortcutsRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/UserA11yQsShortcutsRepositoryTest.kt
new file mode 100644
index 0000000..ce22e28
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/UserA11yQsShortcutsRepositoryTest.kt
@@ -0,0 +1,66 @@
+/*
+ * 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.accessibility.data.repository
+
+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.collectLastValue
+import com.android.systemui.util.settings.FakeSettings
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class UserA11yQsShortcutsRepositoryTest : SysuiTestCase() {
+    private val secureSettings = FakeSettings()
+    private val testDispatcher = StandardTestDispatcher()
+    private val testScope = TestScope(testDispatcher)
+
+    private val underTest =
+        UserA11yQsShortcutsRepository(
+            USER_ID,
+            secureSettings,
+            testScope.backgroundScope,
+            testDispatcher
+        )
+
+    @Test
+    fun targetsMatchesSetting() =
+        testScope.runTest {
+            val observedTargets by collectLastValue(underTest.targets)
+            val a11yQsTargets = setOf("a", "b", "c")
+            secureSettings.putStringForUser(
+                SETTING_NAME,
+                a11yQsTargets.joinToString(SEPARATOR),
+                USER_ID
+            )
+
+            assertThat(observedTargets).isEqualTo(a11yQsTargets)
+        }
+
+    companion object {
+        private const val USER_ID = 0
+        private const val SEPARATOR = ":"
+        private const val SETTING_NAME = Settings.Secure.ACCESSIBILITY_QS_TARGETS
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/A11yShortcutAutoAddableListTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/A11yShortcutAutoAddableListTest.kt
new file mode 100644
index 0000000..311122d
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/A11yShortcutAutoAddableListTest.kt
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.pipeline.domain.autoaddable
+
+import android.content.ComponentName
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
+import android.view.accessibility.Flags
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.internal.accessibility.AccessibilityShortcutController
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.tiles.ColorCorrectionTile
+import com.android.systemui.qs.tiles.ColorInversionTile
+import com.android.systemui.qs.tiles.OneHandedModeTile
+import com.android.systemui.qs.tiles.ReduceBrightColorsTile
+import com.android.systemui.util.mockito.mock
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class A11yShortcutAutoAddableListTest : SysuiTestCase() {
+
+    private val factory =
+        object : A11yShortcutAutoAddable.Factory {
+            override fun create(
+                spec: TileSpec,
+                componentName: ComponentName
+            ): A11yShortcutAutoAddable {
+                return A11yShortcutAutoAddable(mock(), mock(), spec, componentName)
+            }
+        }
+
+    @Test
+    @DisableFlags(Flags.FLAG_A11Y_QS_SHORTCUT)
+    fun getA11yShortcutAutoAddables_withA11yQsShortcutFlagOff_emptyResult() {
+        val autoAddables = A11yShortcutAutoAddableList.getA11yShortcutAutoAddables(factory)
+
+        assertThat(autoAddables).isEmpty()
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_A11Y_QS_SHORTCUT)
+    fun getA11yShortcutAutoAddables_withA11yQsShortcutFlagOn_correctAutoAddables() {
+        val expected =
+            setOf(
+                factory.create(
+                    TileSpec.create(ColorCorrectionTile.TILE_SPEC),
+                    AccessibilityShortcutController.DALTONIZER_COMPONENT_NAME
+                ),
+                factory.create(
+                    TileSpec.create(ColorInversionTile.TILE_SPEC),
+                    AccessibilityShortcutController.COLOR_INVERSION_COMPONENT_NAME
+                ),
+                factory.create(
+                    TileSpec.create(OneHandedModeTile.TILE_SPEC),
+                    AccessibilityShortcutController.ONE_HANDED_COMPONENT_NAME
+                ),
+                factory.create(
+                    TileSpec.create(ReduceBrightColorsTile.TILE_SPEC),
+                    AccessibilityShortcutController.REDUCE_BRIGHT_COLORS_COMPONENT_NAME
+                ),
+            )
+
+        val autoAddables = A11yShortcutAutoAddableList.getA11yShortcutAutoAddables(factory)
+
+        assertThat(autoAddables).isNotEmpty()
+        assertThat(autoAddables).containsExactlyElementsIn(expected)
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/A11yShortcutAutoAddableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/A11yShortcutAutoAddableTest.kt
new file mode 100644
index 0000000..3b33a43
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/A11yShortcutAutoAddableTest.kt
@@ -0,0 +1,175 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.pipeline.domain.autoaddable
+
+import android.content.ComponentName
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.accessibility.data.repository.FakeAccessibilityQsShortcutsRepository
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.coroutines.collectValues
+import com.android.systemui.qs.pipeline.domain.model.AutoAddSignal
+import com.android.systemui.qs.pipeline.domain.model.AutoAddTracking
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class A11yShortcutAutoAddableTest : SysuiTestCase() {
+    private val testDispatcher = StandardTestDispatcher()
+    private val testScope = TestScope(testDispatcher)
+
+    private val a11yQsShortcutsRepository = FakeAccessibilityQsShortcutsRepository()
+    private val underTest =
+        A11yShortcutAutoAddable(a11yQsShortcutsRepository, testDispatcher, SPEC, TARGET_COMPONENT)
+
+    @Test
+    fun settingNotSet_noSignal() =
+        testScope.runTest {
+            val signal by collectLastValue(underTest.autoAddSignal(USER_ID))
+
+            assertThat(signal).isNull() // null means no emitted value
+        }
+
+    @Test
+    fun settingSetWithTarget_addSignal() =
+        testScope.runTest {
+            val signal by collectLastValue(underTest.autoAddSignal(USER_ID))
+            assertThat(signal).isNull()
+
+            a11yQsShortcutsRepository.setA11yQsShortcutTargets(
+                USER_ID,
+                setOf(TARGET_COMPONENT_FLATTEN)
+            )
+
+            assertThat(signal).isEqualTo(AutoAddSignal.Add(SPEC))
+        }
+
+    @Test
+    fun settingSetWithoutTarget_removeSignal() =
+        testScope.runTest {
+            val signal by collectLastValue(flow = underTest.autoAddSignal(USER_ID))
+            assertThat(signal).isNull()
+
+            a11yQsShortcutsRepository.setA11yQsShortcutTargets(
+                USER_ID,
+                setOf(OTHER_COMPONENT_FLATTEN)
+            )
+
+            assertThat(signal).isEqualTo(AutoAddSignal.Remove(SPEC))
+        }
+
+    @Test
+    fun settingSetWithMultipleComponents_containsTarget_addSignal() =
+        testScope.runTest {
+            val signal by collectLastValue(underTest.autoAddSignal(USER_ID))
+            assertThat(signal).isNull()
+
+            a11yQsShortcutsRepository.setA11yQsShortcutTargets(
+                USER_ID,
+                setOf(OTHER_COMPONENT_FLATTEN, TARGET_COMPONENT_FLATTEN)
+            )
+
+            assertThat(signal).isEqualTo(AutoAddSignal.Add(SPEC))
+        }
+
+    @Test
+    fun settingSetWithMultipleComponents_doesNotContainTarget_removeSignal() =
+        testScope.runTest {
+            val signal by collectLastValue(underTest.autoAddSignal(USER_ID))
+            assertThat(signal).isNull()
+
+            a11yQsShortcutsRepository.setA11yQsShortcutTargets(
+                USER_ID,
+                setOf(OTHER_COMPONENT_FLATTEN, OTHER_COMPONENT_FLATTEN)
+            )
+
+            assertThat(signal).isEqualTo(AutoAddSignal.Remove(SPEC))
+        }
+
+    @Test
+    fun multipleChangesWithTarget_onlyOneAddSignal() =
+        testScope.runTest {
+            val signals by collectValues(underTest.autoAddSignal(USER_ID))
+            assertThat(signals).isEmpty()
+
+            repeat(3) {
+                a11yQsShortcutsRepository.setA11yQsShortcutTargets(
+                    USER_ID,
+                    setOf(TARGET_COMPONENT_FLATTEN)
+                )
+            }
+
+            assertThat(signals.size).isEqualTo(1)
+            assertThat(signals[0]).isEqualTo(AutoAddSignal.Add(SPEC))
+        }
+
+    @Test
+    fun multipleChangesWithoutTarget_onlyOneRemoveSignal() =
+        testScope.runTest {
+            val signals by collectValues(underTest.autoAddSignal(USER_ID))
+            assertThat(signals).isEmpty()
+
+            repeat(3) {
+                a11yQsShortcutsRepository.setA11yQsShortcutTargets(
+                    USER_ID,
+                    setOf("$OTHER_COMPONENT_FLATTEN$it")
+                )
+            }
+
+            assertThat(signals.size).isEqualTo(1)
+            assertThat(signals[0]).isEqualTo(AutoAddSignal.Remove(SPEC))
+        }
+
+    @Test
+    fun settingSetWithTargetForUsers_onlySignalInThatUser() =
+        testScope.runTest {
+            val otherUserId = USER_ID + 1
+            val signalTargetUser by collectLastValue(underTest.autoAddSignal(USER_ID))
+            val signalOtherUser by collectLastValue(underTest.autoAddSignal(otherUserId))
+            assertThat(signalTargetUser).isNull()
+            assertThat(signalOtherUser).isNull()
+
+            a11yQsShortcutsRepository.setA11yQsShortcutTargets(
+                USER_ID,
+                setOf(TARGET_COMPONENT_FLATTEN)
+            )
+
+            assertThat(signalTargetUser).isEqualTo(AutoAddSignal.Add(SPEC))
+            assertThat(signalOtherUser).isNull()
+        }
+
+    @Test
+    fun strategyAlways() {
+        assertThat(underTest.autoAddTracking).isEqualTo(AutoAddTracking.Always)
+    }
+
+    companion object {
+        private val SPEC = TileSpec.create("spec")
+        private val TARGET_COMPONENT = ComponentName("FakePkgName", "FakeClassName")
+        private val TARGET_COMPONENT_FLATTEN = TARGET_COMPONENT.flattenToString()
+        private val OTHER_COMPONENT_FLATTEN =
+            ComponentName("FakePkgName", "OtherClassName").flattenToString()
+        private const val USER_ID = 0
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/AccessibilityModule.kt b/packages/SystemUI/src/com/android/systemui/accessibility/AccessibilityModule.kt
index 8c2d221..35f9344 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/AccessibilityModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/AccessibilityModule.kt
@@ -16,6 +16,8 @@
 
 package com.android.systemui.accessibility
 
+import com.android.systemui.accessibility.data.repository.AccessibilityQsShortcutsRepository
+import com.android.systemui.accessibility.data.repository.AccessibilityQsShortcutsRepositoryImpl
 import com.android.systemui.accessibility.data.repository.ColorCorrectionRepository
 import com.android.systemui.accessibility.data.repository.ColorCorrectionRepositoryImpl
 import com.android.systemui.accessibility.data.repository.ColorInversionRepository
@@ -31,4 +33,9 @@
 
     @Binds
     fun colorInversionRepository(impl: ColorInversionRepositoryImpl): ColorInversionRepository
+
+    @Binds
+    fun accessibilityQsShortcutsRepository(
+        impl: AccessibilityQsShortcutsRepositoryImpl
+    ): AccessibilityQsShortcutsRepository
 }
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/AccessibilityQsShortcutsRepository.kt b/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/AccessibilityQsShortcutsRepository.kt
new file mode 100644
index 0000000..401ac0f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/AccessibilityQsShortcutsRepository.kt
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.accessibility.data.repository
+
+import android.util.SparseArray
+import androidx.annotation.GuardedBy
+import com.android.systemui.dagger.SysUISingleton
+import javax.inject.Inject
+import kotlinx.coroutines.flow.SharedFlow
+
+/** Provides data related to accessibility quick setting shortcut option. */
+interface AccessibilityQsShortcutsRepository {
+    /**
+     * Observable for the a11y features the user chooses in the Settings app to use the quick
+     * setting option.
+     */
+    fun a11yQsShortcutTargets(userId: Int): SharedFlow<Set<String>>
+}
+
+@SysUISingleton
+class AccessibilityQsShortcutsRepositoryImpl
+@Inject
+constructor(
+    private val userA11yQsShortcutsRepositoryFactory: UserA11yQsShortcutsRepository.Factory,
+) : AccessibilityQsShortcutsRepository {
+
+    @GuardedBy("userA11yQsShortcutsRepositories")
+    private val userA11yQsShortcutsRepositories = SparseArray<UserA11yQsShortcutsRepository>()
+
+    override fun a11yQsShortcutTargets(userId: Int): SharedFlow<Set<String>> {
+        return synchronized(userA11yQsShortcutsRepositories) {
+            if (userId !in userA11yQsShortcutsRepositories) {
+                val userA11yQsShortcutsRepository =
+                    userA11yQsShortcutsRepositoryFactory.create(userId)
+                userA11yQsShortcutsRepositories.put(userId, userA11yQsShortcutsRepository)
+            }
+            userA11yQsShortcutsRepositories.get(userId).targets
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/UserA11yQsShortcutsRepository.kt b/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/UserA11yQsShortcutsRepository.kt
new file mode 100644
index 0000000..ed91f03
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/UserA11yQsShortcutsRepository.kt
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.accessibility.data.repository
+
+import android.provider.Settings
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.qs.pipeline.data.repository.TileSpecRepository
+import com.android.systemui.util.settings.SecureSettings
+import com.android.systemui.util.settings.SettingsProxyExt.observerFlow
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onStart
+import kotlinx.coroutines.flow.shareIn
+
+/**
+ * Single user version of [AccessibilityQsShortcutsRepository]. It provides a similar interface as
+ * [TileSpecRepository], but focusing solely on the user it was created for. It observes the changes
+ * on the [Settings.Secure.ACCESSIBILITY_QS_TARGETS] for a given user
+ */
+class UserA11yQsShortcutsRepository
+@AssistedInject
+constructor(
+    @Assisted private val userId: Int,
+    private val secureSettings: SecureSettings,
+    @Application private val applicationScope: CoroutineScope,
+    @Background private val backgroundDispatcher: CoroutineDispatcher,
+) {
+    val targets =
+        secureSettings
+            .observerFlow(userId, SETTING_NAME)
+            // Force an update
+            .onStart { emit(Unit) }
+            .map { getA11yQsShortcutTargets(userId) }
+            .flowOn(backgroundDispatcher)
+            .shareIn(
+                scope = applicationScope,
+                started = SharingStarted.WhileSubscribed(),
+                replay = 1
+            )
+
+    private fun getA11yQsShortcutTargets(userId: Int): Set<String> {
+        val settingValue = secureSettings.getStringForUser(SETTING_NAME, userId) ?: ""
+        return settingValue.split(SETTING_SEPARATOR).filterNot { it.isEmpty() }.toSet()
+    }
+
+    companion object {
+        const val SETTING_NAME = Settings.Secure.ACCESSIBILITY_QS_TARGETS
+        const val SETTING_SEPARATOR = ":"
+    }
+
+    @AssistedFactory
+    interface Factory {
+        fun create(
+            userId: Int,
+        ): UserA11yQsShortcutsRepository
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/BaseAutoAddableModule.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/BaseAutoAddableModule.kt
index adea26e..e1ec338 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/BaseAutoAddableModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/BaseAutoAddableModule.kt
@@ -18,6 +18,8 @@
 
 import android.content.res.Resources
 import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.qs.pipeline.domain.autoaddable.A11yShortcutAutoAddable
+import com.android.systemui.qs.pipeline.domain.autoaddable.A11yShortcutAutoAddableList
 import com.android.systemui.qs.pipeline.domain.autoaddable.AutoAddableSetting
 import com.android.systemui.qs.pipeline.domain.autoaddable.AutoAddableSettingList
 import com.android.systemui.qs.pipeline.domain.autoaddable.CastAutoAddable
@@ -51,6 +53,16 @@
                 )
                 .toSet()
         }
+
+        @Provides
+        @ElementsIntoSet
+        fun providesA11yShortcutAutoAddable(
+            a11yShortcutAutoAddableFactory: A11yShortcutAutoAddable.Factory
+        ): Set<AutoAddable> {
+            return A11yShortcutAutoAddableList.getA11yShortcutAutoAddables(
+                a11yShortcutAutoAddableFactory
+            )
+        }
     }
 
     @Binds @IntoSet fun bindCastAutoAddable(impl: CastAutoAddable): AutoAddable
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/A11yShortcutAutoAddable.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/A11yShortcutAutoAddable.kt
new file mode 100644
index 0000000..2cebbe3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/A11yShortcutAutoAddable.kt
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.pipeline.domain.autoaddable
+
+import android.content.ComponentName
+import android.provider.Settings
+import com.android.systemui.accessibility.data.repository.AccessibilityQsShortcutsRepository
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.qs.pipeline.domain.model.AutoAddSignal
+import com.android.systemui.qs.pipeline.domain.model.AutoAddTracking
+import com.android.systemui.qs.pipeline.domain.model.AutoAddable
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import java.util.Objects
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.map
+
+/**
+ * [A11yShortcutAutoAddable] will auto add/remove qs tile of the accessibility framework feature
+ * based on the user's choices in the Settings app.
+ *
+ * The a11y feature component is added to [Settings.Secure.ACCESSIBILITY_QS_TARGETS] when the user
+ * selects to use qs tile as a shortcut for the a11 feature in the Settings app. The accessibility
+ * feature component is removed from [Settings.Secure.ACCESSIBILITY_QS_TARGETS] when the user
+ * doesn't want to use qs tile as a shortcut for the a11y feature in the Settings app.
+ *
+ * [A11yShortcutAutoAddable] tracks a [Settings.Secure.ACCESSIBILITY_QS_TARGETS] and when its value
+ * changes, it will emit a [AutoAddSignal.Add] for the [spec] if the [componentName] is a substring
+ * of the value; it will emit a [AutoAddSignal.Remove] for the [spec] if the [componentName] is not
+ * a substring of the value.
+ */
+class A11yShortcutAutoAddable
+@AssistedInject
+constructor(
+    private val a11yQsShortcutsRepository: AccessibilityQsShortcutsRepository,
+    @Background private val bgDispatcher: CoroutineDispatcher,
+    @Assisted private val spec: TileSpec,
+    @Assisted private val componentName: ComponentName
+) : AutoAddable {
+
+    override fun autoAddSignal(userId: Int): Flow<AutoAddSignal> {
+        return a11yQsShortcutsRepository
+            .a11yQsShortcutTargets(userId)
+            .map { it.contains(componentName.flattenToString()) }
+            .filterNotNull()
+            .distinctUntilChanged()
+            .map { if (it) AutoAddSignal.Add(spec) else AutoAddSignal.Remove(spec) }
+            .flowOn(bgDispatcher)
+    }
+
+    override val autoAddTracking = AutoAddTracking.Always
+
+    override val description =
+        "A11yShortcutAutoAddableSetting: $spec:$componentName ($autoAddTracking)"
+
+    override fun equals(other: Any?): Boolean {
+        return other is A11yShortcutAutoAddable &&
+            spec == other.spec &&
+            componentName == other.componentName
+    }
+
+    override fun hashCode(): Int {
+        return Objects.hash(spec, componentName)
+    }
+
+    override fun toString(): String {
+        return description
+    }
+
+    @AssistedFactory
+    interface Factory {
+        fun create(spec: TileSpec, componentName: ComponentName): A11yShortcutAutoAddable
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/A11yShortcutAutoAddableList.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/A11yShortcutAutoAddableList.kt
new file mode 100644
index 0000000..08e3920
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/A11yShortcutAutoAddableList.kt
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.pipeline.domain.autoaddable
+
+import android.view.accessibility.Flags
+import com.android.internal.accessibility.AccessibilityShortcutController
+import com.android.systemui.qs.pipeline.domain.model.AutoAddable
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.tiles.ColorCorrectionTile
+import com.android.systemui.qs.tiles.ColorInversionTile
+import com.android.systemui.qs.tiles.OneHandedModeTile
+import com.android.systemui.qs.tiles.ReduceBrightColorsTile
+
+object A11yShortcutAutoAddableList {
+
+    /**
+     * Generate a collection of [A11yShortcutAutoAddable] for the framework tiles related to
+     * accessibility features with shortcut options
+     */
+    fun getA11yShortcutAutoAddables(factory: A11yShortcutAutoAddable.Factory): Set<AutoAddable> {
+        return if (Flags.a11yQsShortcut()) {
+            setOf(
+                factory.create(
+                    TileSpec.create(ColorCorrectionTile.TILE_SPEC),
+                    AccessibilityShortcutController.DALTONIZER_COMPONENT_NAME
+                ),
+                factory.create(
+                    TileSpec.create(ColorInversionTile.TILE_SPEC),
+                    AccessibilityShortcutController.COLOR_INVERSION_COMPONENT_NAME
+                ),
+                factory.create(
+                    TileSpec.create(OneHandedModeTile.TILE_SPEC),
+                    AccessibilityShortcutController.ONE_HANDED_COMPONENT_NAME
+                ),
+                factory.create(
+                    TileSpec.create(ReduceBrightColorsTile.TILE_SPEC),
+                    AccessibilityShortcutController.REDUCE_BRIGHT_COLORS_COMPONENT_NAME
+                ),
+            )
+        } else {
+            emptySet()
+        }
+    }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/accessibility/data/repository/FakeAccessibilityQsShortcutsRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/accessibility/data/repository/FakeAccessibilityQsShortcutsRepository.kt
new file mode 100644
index 0000000..e547da1
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/accessibility/data/repository/FakeAccessibilityQsShortcutsRepository.kt
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.accessibility.data.repository
+
+import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.SharedFlow
+import kotlinx.coroutines.flow.asSharedFlow
+
+class FakeAccessibilityQsShortcutsRepository : AccessibilityQsShortcutsRepository {
+
+    private val targetsPerUser = mutableMapOf<Int, MutableSharedFlow<Set<String>>>()
+
+    override fun a11yQsShortcutTargets(userId: Int): SharedFlow<Set<String>> {
+        return getFlow(userId).asSharedFlow()
+    }
+
+    /**
+     * Set the a11y qs shortcut targets. In real world, the A11y QS Shortcut targets are set by the
+     * Settings app not in SysUi
+     */
+    suspend fun setA11yQsShortcutTargets(userId: Int, targets: Set<String>) {
+        getFlow(userId).emit(targets)
+    }
+
+    private fun getFlow(userId: Int): MutableSharedFlow<Set<String>> =
+        targetsPerUser.getOrPut(userId) { MutableSharedFlow(replay = 1) }
+}