diff options
| author | 2024-01-25 00:14:23 +0000 | |
|---|---|---|
| committer | 2024-01-25 00:14:23 +0000 | |
| commit | ca8768842a6b7044d8fe9fce83ffc0a6e074c7cf (patch) | |
| tree | 3691fa83c1fe4b4ddac7e08df4601a6db3e8008e | |
| parent | 684fbcb540c371382f1101aa34a9f1429d6bafda (diff) | |
| parent | 8960fbf3aaa39782f4a93c4c2d61693f90f5131e (diff) | |
Merge "Auto Add/Remove a11y tiles when the ACCESSIBILITY_QS_TILES updates" into main
11 files changed, 754 insertions, 0 deletions
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 000000000000..9287edf4ee51 --- /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 000000000000..ce22e288e292 --- /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 000000000000..311122d7f8d5 --- /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 000000000000..3b33a43d9341 --- /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 8c2d221e3f97..35f9344ae897 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 @@ interface AccessibilityModule { @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 000000000000..401ac0f9337b --- /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 000000000000..ed91f03cc56e --- /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 adea26e5aa26..e1ec338cec6f 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 @@ package com.android.systemui.qs.pipeline.dagger 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 @@ interface BaseAutoAddableModule { ) .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 000000000000..2cebbe3f372c --- /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 000000000000..08e39204386e --- /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 000000000000..e547da1b92dd --- /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) } +} |