1 /*
<lambda>null2  * Copyright (C) 2023 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.systemui.accessibility.data.repository
18 
19 import android.annotation.SuppressLint
20 import android.content.ComponentName
21 import android.content.Context
22 import android.text.TextUtils
23 import android.util.SparseArray
24 import android.view.accessibility.AccessibilityManager
25 import androidx.annotation.GuardedBy
26 import com.android.internal.accessibility.AccessibilityShortcutController
27 import com.android.systemui.dagger.SysUISingleton
28 import com.android.systemui.dagger.qualifiers.Background
29 import com.android.systemui.qs.pipeline.shared.TileSpec
30 import com.android.systemui.qs.tiles.ColorCorrectionTile
31 import com.android.systemui.qs.tiles.ColorInversionTile
32 import com.android.systemui.qs.tiles.FontScalingTile
33 import com.android.systemui.qs.tiles.HearingDevicesTile
34 import com.android.systemui.qs.tiles.OneHandedModeTile
35 import com.android.systemui.qs.tiles.ReduceBrightColorsTile
36 import javax.inject.Inject
37 import kotlinx.coroutines.CoroutineDispatcher
38 import kotlinx.coroutines.Deferred
39 import kotlinx.coroutines.async
40 import kotlinx.coroutines.coroutineScope
41 import kotlinx.coroutines.flow.SharedFlow
42 import kotlinx.coroutines.withContext
43 
44 /** Provides data related to accessibility quick setting shortcut option. */
45 interface AccessibilityQsShortcutsRepository {
46     /**
47      * Observable for the a11y features the user chooses in the Settings app to use the quick
48      * setting option.
49      */
50     fun a11yQsShortcutTargets(userId: Int): SharedFlow<Set<String>>
51 
52     /** Notify accessibility manager that there are changes to the accessibility tiles */
53     suspend fun notifyAccessibilityManagerTilesChanged(userContext: Context, tiles: List<TileSpec>)
54 }
55 
56 @SysUISingleton
57 class AccessibilityQsShortcutsRepositoryImpl
58 @Inject
59 constructor(
60     private val manager: AccessibilityManager,
61     private val userA11yQsShortcutsRepositoryFactory: UserA11yQsShortcutsRepository.Factory,
62     @Background private val backgroundDispatcher: CoroutineDispatcher,
63 ) : AccessibilityQsShortcutsRepository {
64     companion object {
65         val TILE_SPEC_TO_COMPONENT_MAPPING =
66             mapOf(
67                 ColorCorrectionTile.TILE_SPEC to
68                     AccessibilityShortcutController.DALTONIZER_TILE_COMPONENT_NAME,
69                 ColorInversionTile.TILE_SPEC to
70                     AccessibilityShortcutController.COLOR_INVERSION_TILE_COMPONENT_NAME,
71                 OneHandedModeTile.TILE_SPEC to
72                     AccessibilityShortcutController.ONE_HANDED_TILE_COMPONENT_NAME,
73                 ReduceBrightColorsTile.TILE_SPEC to
74                     AccessibilityShortcutController
75                         .REDUCE_BRIGHT_COLORS_TILE_SERVICE_COMPONENT_NAME,
76                 FontScalingTile.TILE_SPEC to
77                     AccessibilityShortcutController.FONT_SIZE_TILE_COMPONENT_NAME,
78                 HearingDevicesTile.TILE_SPEC to
79                     AccessibilityShortcutController.ACCESSIBILITY_HEARING_AIDS_TILE_COMPONENT_NAME
80             )
81     }
82 
83     @GuardedBy("userA11yQsShortcutsRepositories")
84     private val userA11yQsShortcutsRepositories = SparseArray<UserA11yQsShortcutsRepository>()
85 
a11yQsShortcutTargetsnull86     override fun a11yQsShortcutTargets(userId: Int): SharedFlow<Set<String>> {
87         return synchronized(userA11yQsShortcutsRepositories) {
88             if (userId !in userA11yQsShortcutsRepositories) {
89                 val userA11yQsShortcutsRepository =
90                     userA11yQsShortcutsRepositoryFactory.create(userId)
91                 userA11yQsShortcutsRepositories.put(userId, userA11yQsShortcutsRepository)
92             }
93             userA11yQsShortcutsRepositories.get(userId).targets
94         }
95     }
96 
97     @SuppressLint("MissingPermission") // android.permission.STATUS_BAR_SERVICE
notifyAccessibilityManagerTilesChangednull98     override suspend fun notifyAccessibilityManagerTilesChanged(
99         userContext: Context,
100         tiles: List<TileSpec>
101     ) {
102         val newTiles = mutableListOf<ComponentName>()
103         val accessibilityTileServices = getAccessibilityTileServices(userContext)
104         tiles.forEach { tileSpec ->
105             when (tileSpec) {
106                 is TileSpec.CustomTileSpec -> {
107                     if (accessibilityTileServices.contains(tileSpec.componentName)) {
108                         newTiles.add(tileSpec.componentName)
109                     }
110                 }
111                 is TileSpec.PlatformTileSpec -> {
112                     if (TILE_SPEC_TO_COMPONENT_MAPPING.containsKey(tileSpec.spec)) {
113                         newTiles.add(TILE_SPEC_TO_COMPONENT_MAPPING[tileSpec.spec]!!)
114                     }
115                 }
116                 TileSpec.Invalid -> {
117                     // do nothing
118                 }
119             }
120         }
121 
122         withContext(backgroundDispatcher) {
123             manager.notifyQuickSettingsTilesChanged(userContext.userId, newTiles)
124         }
125     }
126 
getAccessibilityTileServicesnull127     private suspend fun getAccessibilityTileServices(context: Context): Set<ComponentName> =
128         coroutineScope {
129             val a11yServiceTileServices: Deferred<Set<ComponentName>> =
130                 async(backgroundDispatcher) {
131                     manager.installedAccessibilityServiceList
132                         .mapNotNull {
133                             val packageName = it.resolveInfo.serviceInfo.packageName
134                             val tileServiceClass = it.tileServiceName
135 
136                             if (!TextUtils.isEmpty(tileServiceClass)) {
137                                 ComponentName(packageName, tileServiceClass!!)
138                             } else {
139                                 null
140                             }
141                         }
142                         .toSet()
143                 }
144 
145             val a11yShortcutInfoTileServices: Deferred<Set<ComponentName>> =
146                 async(backgroundDispatcher) {
147                     manager
148                         .getInstalledAccessibilityShortcutListAsUser(context, context.userId)
149                         .mapNotNull {
150                             val packageName = it.componentName.packageName
151                             val tileServiceClass = it.tileServiceName
152 
153                             if (!TextUtils.isEmpty(tileServiceClass)) {
154                                 ComponentName(packageName, tileServiceClass!!)
155                             } else {
156                                 null
157                             }
158                         }
159                         .toSet()
160                 }
161 
162             a11yServiceTileServices.await() + a11yShortcutInfoTileServices.await()
163         }
164 }
165