1 /*
2  * Copyright (C) 2020 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.internal.accessibility.dialog;
18 
19 import static com.android.internal.accessibility.AccessibilityShortcutController.ACCESSIBILITY_HEARING_AIDS_COMPONENT_NAME;
20 import static com.android.internal.accessibility.AccessibilityShortcutController.COLOR_INVERSION_COMPONENT_NAME;
21 import static com.android.internal.accessibility.AccessibilityShortcutController.DALTONIZER_COMPONENT_NAME;
22 import static com.android.internal.accessibility.AccessibilityShortcutController.MAGNIFICATION_CONTROLLER_NAME;
23 import static com.android.internal.accessibility.AccessibilityShortcutController.ONE_HANDED_COMPONENT_NAME;
24 import static com.android.internal.accessibility.AccessibilityShortcutController.REDUCE_BRIGHT_COLORS_COMPONENT_NAME;
25 import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.SOFTWARE;
26 import static com.android.internal.accessibility.util.AccessibilityUtils.getAccessibilityServiceFragmentType;
27 import static com.android.internal.accessibility.util.ShortcutUtils.isShortcutContained;
28 import static com.android.internal.os.RoSystemProperties.SUPPORT_ONE_HANDED_MODE;
29 
30 import android.accessibilityservice.AccessibilityServiceInfo;
31 import android.accessibilityservice.AccessibilityShortcutInfo;
32 import android.annotation.NonNull;
33 import android.app.ActivityManager;
34 import android.content.ComponentName;
35 import android.content.Context;
36 import android.os.Build;
37 import android.os.UserHandle;
38 import android.provider.Settings;
39 import android.view.accessibility.AccessibilityManager;
40 
41 import com.android.internal.R;
42 import com.android.internal.accessibility.common.ShortcutConstants.AccessibilityFragmentType;
43 import com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType;
44 
45 import java.util.ArrayList;
46 import java.util.Collections;
47 import java.util.List;
48 
49 /**
50  * Collection of utilities for accessibility target.
51  */
52 public final class AccessibilityTargetHelper {
AccessibilityTargetHelper()53     private AccessibilityTargetHelper() {}
54 
55     /**
56      * Returns list of {@link AccessibilityTarget} of assigned accessibility shortcuts from
57      * {@link AccessibilityManager#getAccessibilityShortcutTargets} including accessibility
58      * feature's package name, component id, etc.
59      *
60      * @param context The context of the application.
61      * @param shortcutType The shortcut type.
62      * @return The list of {@link AccessibilityTarget}.
63      * @hide
64      */
getTargets(Context context, @UserShortcutType int shortcutType)65     public static List<AccessibilityTarget> getTargets(Context context,
66             @UserShortcutType int shortcutType) {
67         // List all accessibility target
68         final List<AccessibilityTarget> installedTargets = getInstalledTargets(context,
69                 shortcutType);
70 
71         // List accessibility shortcut target
72         final AccessibilityManager am = (AccessibilityManager) context.getSystemService(
73                 Context.ACCESSIBILITY_SERVICE);
74         final List<String> assignedTargets = am.getAccessibilityShortcutTargets(shortcutType);
75 
76         // Get the list of accessibility shortcut target in all accessibility target
77         final List<AccessibilityTarget> results = new ArrayList<>();
78         for (String assignedTarget : assignedTargets) {
79             for (AccessibilityTarget installedTarget : installedTargets) {
80                 if (!MAGNIFICATION_CONTROLLER_NAME.contentEquals(assignedTarget)) {
81                     final ComponentName assignedTargetComponentName =
82                             ComponentName.unflattenFromString(assignedTarget);
83                     final ComponentName targetComponentName = ComponentName.unflattenFromString(
84                             installedTarget.getId());
85                     if (assignedTargetComponentName.equals(targetComponentName)) {
86                         results.add(installedTarget);
87                         continue;
88                     }
89                 }
90                 if (assignedTarget.contentEquals(installedTarget.getId())) {
91                     results.add(installedTarget);
92                 }
93             }
94         }
95         return results;
96     }
97 
98     /**
99      * Returns list of {@link AccessibilityTarget} of the installed accessibility service,
100      * accessibility activity, and allowlisting feature including accessibility feature's package
101      * name, component id, etc.
102      *
103      * @param context The context of the application.
104      * @param shortcutType The shortcut type.
105      * @return The list of {@link AccessibilityTarget}.
106      * @hide
107      */
getInstalledTargets(Context context, @UserShortcutType int shortcutType)108     public static List<AccessibilityTarget> getInstalledTargets(Context context,
109             @UserShortcutType int shortcutType) {
110         final List<AccessibilityTarget> targets = new ArrayList<>();
111         targets.addAll(getAccessibilityFilteredTargets(context, shortcutType));
112         targets.addAll(getAllowListingFeatureTargets(context, shortcutType));
113 
114         return targets;
115     }
116 
getAccessibilityFilteredTargets(Context context, @UserShortcutType int shortcutType)117     private static List<AccessibilityTarget> getAccessibilityFilteredTargets(Context context,
118             @UserShortcutType int shortcutType) {
119         final List<AccessibilityTarget> serviceTargets =
120                 getAccessibilityServiceTargets(context, shortcutType);
121         final List<AccessibilityTarget> activityTargets =
122                 getAccessibilityActivityTargets(context, shortcutType);
123 
124         for (AccessibilityTarget activityTarget : activityTargets) {
125             serviceTargets.removeIf(
126                     serviceTarget -> arePackageNameAndLabelTheSame(serviceTarget, activityTarget));
127         }
128 
129         final List<AccessibilityTarget> targets = new ArrayList<>();
130         targets.addAll(serviceTargets);
131         targets.addAll(activityTargets);
132 
133         return targets;
134     }
135 
arePackageNameAndLabelTheSame(@onNull AccessibilityTarget serviceTarget, @NonNull AccessibilityTarget activityTarget)136     private static boolean arePackageNameAndLabelTheSame(@NonNull AccessibilityTarget serviceTarget,
137             @NonNull AccessibilityTarget activityTarget) {
138         final ComponentName serviceComponentName =
139                 ComponentName.unflattenFromString(serviceTarget.getId());
140         final ComponentName activityComponentName =
141                 ComponentName.unflattenFromString(activityTarget.getId());
142         final boolean isSamePackageName = activityComponentName.getPackageName().equals(
143                 serviceComponentName.getPackageName());
144         final boolean isSameLabel = activityTarget.getLabel().equals(
145                 serviceTarget.getLabel());
146 
147         return isSamePackageName && isSameLabel;
148     }
149 
getAccessibilityServiceTargets(Context context, @UserShortcutType int shortcutType)150     private static List<AccessibilityTarget> getAccessibilityServiceTargets(Context context,
151             @UserShortcutType int shortcutType) {
152         final AccessibilityManager am = (AccessibilityManager) context.getSystemService(
153                 Context.ACCESSIBILITY_SERVICE);
154         final List<AccessibilityServiceInfo> installedServices =
155                 am.getInstalledAccessibilityServiceList();
156         if (installedServices == null) {
157             return Collections.emptyList();
158         }
159 
160         final List<AccessibilityTarget> targets = new ArrayList<>(installedServices.size());
161         for (AccessibilityServiceInfo info : installedServices) {
162             final int targetSdk =
163                     info.getResolveInfo().serviceInfo.applicationInfo.targetSdkVersion;
164             final boolean hasRequestAccessibilityButtonFlag =
165                     (info.flags & AccessibilityServiceInfo.FLAG_REQUEST_ACCESSIBILITY_BUTTON) != 0;
166             if ((targetSdk <= Build.VERSION_CODES.Q) && !hasRequestAccessibilityButtonFlag
167                     && (shortcutType == SOFTWARE)) {
168                 continue;
169             }
170 
171             targets.add(createAccessibilityServiceTarget(context, shortcutType, info));
172         }
173 
174         return targets;
175     }
176 
getAccessibilityActivityTargets(Context context, @UserShortcutType int shortcutType)177     private static List<AccessibilityTarget> getAccessibilityActivityTargets(Context context,
178             @UserShortcutType int shortcutType) {
179         final AccessibilityManager am = (AccessibilityManager) context.getSystemService(
180                 Context.ACCESSIBILITY_SERVICE);
181         final List<AccessibilityShortcutInfo> installedServices =
182                 am.getInstalledAccessibilityShortcutListAsUser(context,
183                         ActivityManager.getCurrentUser());
184         if (installedServices == null) {
185             return Collections.emptyList();
186         }
187 
188         final List<AccessibilityTarget> targets = new ArrayList<>(installedServices.size());
189         for (AccessibilityShortcutInfo info : installedServices) {
190             targets.add(new AccessibilityActivityTarget(context, shortcutType, info));
191         }
192 
193         return targets;
194     }
195 
getAllowListingFeatureTargets(Context context, @UserShortcutType int shortcutType)196     private static List<AccessibilityTarget> getAllowListingFeatureTargets(Context context,
197             @UserShortcutType int shortcutType) {
198         final List<AccessibilityTarget> targets = new ArrayList<>();
199         final int uid = context.getApplicationInfo().uid;
200 
201         final InvisibleToggleAllowListingFeatureTarget magnification =
202                 new InvisibleToggleAllowListingFeatureTarget(context,
203                         shortcutType,
204                         isShortcutContained(context, shortcutType, MAGNIFICATION_CONTROLLER_NAME),
205                         MAGNIFICATION_CONTROLLER_NAME,
206                         uid,
207                         context.getString(R.string.accessibility_magnification_chooser_text),
208                         context.getDrawable(R.drawable.ic_accessibility_magnification),
209                         Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED);
210         targets.add(magnification);
211 
212         final ToggleAllowListingFeatureTarget daltonizer =
213                 new ToggleAllowListingFeatureTarget(context,
214                         shortcutType,
215                         isShortcutContained(context, shortcutType,
216                                 DALTONIZER_COMPONENT_NAME.flattenToString()),
217                         DALTONIZER_COMPONENT_NAME.flattenToString(),
218                         uid,
219                         context.getString(R.string.color_correction_feature_name),
220                         context.getDrawable(R.drawable.ic_accessibility_color_correction),
221                         Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED);
222         targets.add(daltonizer);
223 
224         final ToggleAllowListingFeatureTarget colorInversion =
225                 new ToggleAllowListingFeatureTarget(context,
226                         shortcutType,
227                         isShortcutContained(context, shortcutType,
228                                 COLOR_INVERSION_COMPONENT_NAME.flattenToString()),
229                         COLOR_INVERSION_COMPONENT_NAME.flattenToString(),
230                         uid,
231                         context.getString(R.string.color_inversion_feature_name),
232                         context.getDrawable(R.drawable.ic_accessibility_color_inversion),
233                         Settings.Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED);
234         targets.add(colorInversion);
235 
236         if (SUPPORT_ONE_HANDED_MODE) {
237             final ToggleAllowListingFeatureTarget oneHandedMode =
238                     new ToggleAllowListingFeatureTarget(context,
239                             shortcutType,
240                             isShortcutContained(context, shortcutType,
241                                     ONE_HANDED_COMPONENT_NAME.flattenToString()),
242                             ONE_HANDED_COMPONENT_NAME.flattenToString(),
243                             uid,
244                             context.getString(R.string.one_handed_mode_feature_name),
245                             context.getDrawable(R.drawable.ic_accessibility_one_handed),
246                             Settings.Secure.ONE_HANDED_MODE_ACTIVATED);
247             targets.add(oneHandedMode);
248         }
249 
250         final ToggleAllowListingFeatureTarget reduceBrightColors =
251                 new ToggleAllowListingFeatureTarget(context,
252                         shortcutType,
253                         isShortcutContained(context, shortcutType,
254                                 REDUCE_BRIGHT_COLORS_COMPONENT_NAME.flattenToString()),
255                         REDUCE_BRIGHT_COLORS_COMPONENT_NAME.flattenToString(),
256                         uid,
257                         context.getString(R.string.reduce_bright_colors_feature_name),
258                         context.getDrawable(R.drawable.ic_accessibility_reduce_bright_colors),
259                         Settings.Secure.REDUCE_BRIGHT_COLORS_ACTIVATED);
260         targets.add(reduceBrightColors);
261 
262         final InvisibleToggleAllowListingFeatureTarget hearingAids =
263                 new InvisibleToggleAllowListingFeatureTarget(context,
264                         shortcutType,
265                         isShortcutContained(context, shortcutType,
266                                 ACCESSIBILITY_HEARING_AIDS_COMPONENT_NAME.flattenToString()),
267                         ACCESSIBILITY_HEARING_AIDS_COMPONENT_NAME.flattenToString(),
268                         uid,
269                         context.getString(R.string.hearing_aids_feature_name),
270                         context.getDrawable(R.drawable.ic_accessibility_hearing_aid),
271                         /* key= */ null);
272         targets.add(hearingAids);
273 
274         return targets;
275     }
276 
createAccessibilityServiceTarget(Context context, @UserShortcutType int shortcutType, @NonNull AccessibilityServiceInfo info)277     private static AccessibilityTarget createAccessibilityServiceTarget(Context context,
278             @UserShortcutType int shortcutType, @NonNull AccessibilityServiceInfo info) {
279         switch (getAccessibilityServiceFragmentType(info)) {
280             case AccessibilityFragmentType.VOLUME_SHORTCUT_TOGGLE:
281                 return new VolumeShortcutToggleAccessibilityServiceTarget(context, shortcutType,
282                         info);
283             case AccessibilityFragmentType.INVISIBLE_TOGGLE:
284                 return new InvisibleToggleAccessibilityServiceTarget(context, shortcutType, info);
285             case AccessibilityFragmentType.TOGGLE:
286                 return new ToggleAccessibilityServiceTarget(context, shortcutType, info);
287             default:
288                 throw new IllegalStateException("Unexpected fragment type");
289         }
290     }
291 
292     /**
293      * Determines if the{@link AccessibilityTarget} is allowed.
294      */
isAccessibilityTargetAllowed(Context context, String packageName, int uid)295     public static boolean isAccessibilityTargetAllowed(Context context, String packageName,
296             int uid) {
297         final AccessibilityManager am = context.getSystemService(AccessibilityManager.class);
298         return am.isAccessibilityTargetAllowed(packageName, uid, UserHandle.myUserId());
299     }
300 
301     /**
302      * Sends restricted dialog intent if the accessibility target is disallowed.
303      */
sendRestrictedDialogIntent(Context context, String packageName, int uid)304     public static boolean sendRestrictedDialogIntent(Context context, String packageName, int uid) {
305         final AccessibilityManager am = context.getSystemService(AccessibilityManager.class);
306         return am.sendRestrictedDialogIntent(packageName, uid, UserHandle.myUserId());
307     }
308 }
309