• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 android.view.accessibility.AccessibilityManager.ACCESSIBILITY_BUTTON;
20 
21 import static com.android.internal.accessibility.AccessibilityShortcutController.COLOR_INVERSION_COMPONENT_NAME;
22 import static com.android.internal.accessibility.AccessibilityShortcutController.DALTONIZER_COMPONENT_NAME;
23 import static com.android.internal.accessibility.AccessibilityShortcutController.MAGNIFICATION_CONTROLLER_NAME;
24 import static com.android.internal.accessibility.AccessibilityShortcutController.ONE_HANDED_COMPONENT_NAME;
25 import static com.android.internal.accessibility.AccessibilityShortcutController.REDUCE_BRIGHT_COLORS_COMPONENT_NAME;
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.provider.Settings;
38 import android.text.BidiFormatter;
39 import android.view.LayoutInflater;
40 import android.view.View;
41 import android.view.accessibility.AccessibilityManager;
42 import android.view.accessibility.AccessibilityManager.ShortcutType;
43 import android.widget.Button;
44 import android.widget.ImageView;
45 import android.widget.TextView;
46 
47 import com.android.internal.R;
48 import com.android.internal.accessibility.common.ShortcutConstants.AccessibilityFragmentType;
49 
50 import java.util.ArrayList;
51 import java.util.Collections;
52 import java.util.List;
53 import java.util.Locale;
54 
55 /**
56  * Collection of utilities for accessibility target.
57  */
58 public final class AccessibilityTargetHelper {
AccessibilityTargetHelper()59     private AccessibilityTargetHelper() {}
60 
61     /**
62      * Returns list of {@link AccessibilityTarget} of assigned accessibility shortcuts from
63      * {@link AccessibilityManager#getAccessibilityShortcutTargets} including accessibility
64      * feature's package name, component id, etc.
65      *
66      * @param context The context of the application.
67      * @param shortcutType The shortcut type.
68      * @return The list of {@link AccessibilityTarget}.
69      * @hide
70      */
getTargets(Context context, @ShortcutType int shortcutType)71     public static List<AccessibilityTarget> getTargets(Context context,
72             @ShortcutType int shortcutType) {
73         // List all accessibility target
74         final List<AccessibilityTarget> installedTargets = getInstalledTargets(context,
75                 shortcutType);
76 
77         // List accessibility shortcut target
78         final AccessibilityManager am = (AccessibilityManager) context.getSystemService(
79                 Context.ACCESSIBILITY_SERVICE);
80         final List<String> assignedTargets = am.getAccessibilityShortcutTargets(shortcutType);
81 
82         // Get the list of accessibility shortcut target in all accessibility target
83         final List<AccessibilityTarget> results = new ArrayList<>();
84         for (String assignedTarget : assignedTargets) {
85             for (AccessibilityTarget installedTarget : installedTargets) {
86                 if (!MAGNIFICATION_CONTROLLER_NAME.contentEquals(assignedTarget)) {
87                     final ComponentName assignedTargetComponentName =
88                             ComponentName.unflattenFromString(assignedTarget);
89                     final ComponentName targetComponentName = ComponentName.unflattenFromString(
90                             installedTarget.getId());
91                     if (assignedTargetComponentName.equals(targetComponentName)) {
92                         results.add(installedTarget);
93                         continue;
94                     }
95                 }
96                 if (assignedTarget.contentEquals(installedTarget.getId())) {
97                     results.add(installedTarget);
98                 }
99             }
100         }
101         return results;
102     }
103 
104     /**
105      * Returns list of {@link AccessibilityTarget} of the installed accessibility service,
106      * accessibility activity, and allowlisting feature including accessibility feature's package
107      * name, component id, etc.
108      *
109      * @param context The context of the application.
110      * @param shortcutType The shortcut type.
111      * @return The list of {@link AccessibilityTarget}.
112      */
getInstalledTargets(Context context, @ShortcutType int shortcutType)113     static List<AccessibilityTarget> getInstalledTargets(Context context,
114             @ShortcutType int shortcutType) {
115         final List<AccessibilityTarget> targets = new ArrayList<>();
116         targets.addAll(getAccessibilityFilteredTargets(context, shortcutType));
117         targets.addAll(getAllowListingFeatureTargets(context, shortcutType));
118 
119         return targets;
120     }
121 
getAccessibilityFilteredTargets(Context context, @ShortcutType int shortcutType)122     private static List<AccessibilityTarget> getAccessibilityFilteredTargets(Context context,
123             @ShortcutType int shortcutType) {
124         final List<AccessibilityTarget> serviceTargets =
125                 getAccessibilityServiceTargets(context, shortcutType);
126         final List<AccessibilityTarget> activityTargets =
127                 getAccessibilityActivityTargets(context, shortcutType);
128 
129         for (AccessibilityTarget activityTarget : activityTargets) {
130             serviceTargets.removeIf(
131                     serviceTarget -> arePackageNameAndLabelTheSame(serviceTarget, activityTarget));
132         }
133 
134         final List<AccessibilityTarget> targets = new ArrayList<>();
135         targets.addAll(serviceTargets);
136         targets.addAll(activityTargets);
137 
138         return targets;
139     }
140 
arePackageNameAndLabelTheSame(@onNull AccessibilityTarget serviceTarget, @NonNull AccessibilityTarget activityTarget)141     private static boolean arePackageNameAndLabelTheSame(@NonNull AccessibilityTarget serviceTarget,
142             @NonNull AccessibilityTarget activityTarget) {
143         final ComponentName serviceComponentName =
144                 ComponentName.unflattenFromString(serviceTarget.getId());
145         final ComponentName activityComponentName =
146                 ComponentName.unflattenFromString(activityTarget.getId());
147         final boolean isSamePackageName = activityComponentName.getPackageName().equals(
148                 serviceComponentName.getPackageName());
149         final boolean isSameLabel = activityTarget.getLabel().equals(
150                 serviceTarget.getLabel());
151 
152         return isSamePackageName && isSameLabel;
153     }
154 
getAccessibilityServiceTargets(Context context, @ShortcutType int shortcutType)155     private static List<AccessibilityTarget> getAccessibilityServiceTargets(Context context,
156             @ShortcutType int shortcutType) {
157         final AccessibilityManager am = (AccessibilityManager) context.getSystemService(
158                 Context.ACCESSIBILITY_SERVICE);
159         final List<AccessibilityServiceInfo> installedServices =
160                 am.getInstalledAccessibilityServiceList();
161         if (installedServices == null) {
162             return Collections.emptyList();
163         }
164 
165         final List<AccessibilityTarget> targets = new ArrayList<>(installedServices.size());
166         for (AccessibilityServiceInfo info : installedServices) {
167             final int targetSdk =
168                     info.getResolveInfo().serviceInfo.applicationInfo.targetSdkVersion;
169             final boolean hasRequestAccessibilityButtonFlag =
170                     (info.flags & AccessibilityServiceInfo.FLAG_REQUEST_ACCESSIBILITY_BUTTON) != 0;
171             if ((targetSdk <= Build.VERSION_CODES.Q) && !hasRequestAccessibilityButtonFlag
172                     && (shortcutType == ACCESSIBILITY_BUTTON)) {
173                 continue;
174             }
175 
176             targets.add(createAccessibilityServiceTarget(context, shortcutType, info));
177         }
178 
179         return targets;
180     }
181 
getAccessibilityActivityTargets(Context context, @ShortcutType int shortcutType)182     private static List<AccessibilityTarget> getAccessibilityActivityTargets(Context context,
183             @ShortcutType int shortcutType) {
184         final AccessibilityManager am = (AccessibilityManager) context.getSystemService(
185                 Context.ACCESSIBILITY_SERVICE);
186         final List<AccessibilityShortcutInfo> installedServices =
187                 am.getInstalledAccessibilityShortcutListAsUser(context,
188                         ActivityManager.getCurrentUser());
189         if (installedServices == null) {
190             return Collections.emptyList();
191         }
192 
193         final List<AccessibilityTarget> targets = new ArrayList<>(installedServices.size());
194         for (AccessibilityShortcutInfo info : installedServices) {
195             targets.add(new AccessibilityActivityTarget(context, shortcutType, info));
196         }
197 
198         return targets;
199     }
200 
getAllowListingFeatureTargets(Context context, @ShortcutType int shortcutType)201     private static List<AccessibilityTarget> getAllowListingFeatureTargets(Context context,
202             @ShortcutType int shortcutType) {
203         final List<AccessibilityTarget> targets = new ArrayList<>();
204 
205         final InvisibleToggleAllowListingFeatureTarget magnification =
206                 new InvisibleToggleAllowListingFeatureTarget(context,
207                         shortcutType,
208                         isShortcutContained(context, shortcutType, MAGNIFICATION_CONTROLLER_NAME),
209                         MAGNIFICATION_CONTROLLER_NAME,
210                         context.getString(R.string.accessibility_magnification_chooser_text),
211                         context.getDrawable(R.drawable.ic_accessibility_magnification),
212                         Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED);
213         targets.add(magnification);
214 
215         final ToggleAllowListingFeatureTarget daltonizer =
216                 new ToggleAllowListingFeatureTarget(context,
217                         shortcutType,
218                         isShortcutContained(context, shortcutType,
219                                 DALTONIZER_COMPONENT_NAME.flattenToString()),
220                         DALTONIZER_COMPONENT_NAME.flattenToString(),
221                         context.getString(R.string.color_correction_feature_name),
222                         context.getDrawable(R.drawable.ic_accessibility_color_correction),
223                         Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED);
224         targets.add(daltonizer);
225 
226         final ToggleAllowListingFeatureTarget colorInversion =
227                 new ToggleAllowListingFeatureTarget(context,
228                         shortcutType,
229                         isShortcutContained(context, shortcutType,
230                                 COLOR_INVERSION_COMPONENT_NAME.flattenToString()),
231                         COLOR_INVERSION_COMPONENT_NAME.flattenToString(),
232                         context.getString(R.string.color_inversion_feature_name),
233                         context.getDrawable(R.drawable.ic_accessibility_color_inversion),
234                         Settings.Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED);
235         targets.add(colorInversion);
236 
237         if (SUPPORT_ONE_HANDED_MODE) {
238             final ToggleAllowListingFeatureTarget oneHandedMode =
239                     new ToggleAllowListingFeatureTarget(context,
240                             shortcutType,
241                             isShortcutContained(context, shortcutType,
242                                     ONE_HANDED_COMPONENT_NAME.flattenToString()),
243                             ONE_HANDED_COMPONENT_NAME.flattenToString(),
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                         context.getString(R.string.reduce_bright_colors_feature_name),
257                         context.getDrawable(R.drawable.ic_accessibility_reduce_bright_colors),
258                         Settings.Secure.REDUCE_BRIGHT_COLORS_ACTIVATED);
259         targets.add(reduceBrightColors);
260 
261         return targets;
262     }
263 
createAccessibilityServiceTarget(Context context, @ShortcutType int shortcutType, @NonNull AccessibilityServiceInfo info)264     private static AccessibilityTarget createAccessibilityServiceTarget(Context context,
265             @ShortcutType int shortcutType, @NonNull AccessibilityServiceInfo info) {
266         switch (getAccessibilityServiceFragmentType(info)) {
267             case AccessibilityFragmentType.VOLUME_SHORTCUT_TOGGLE:
268                 return new VolumeShortcutToggleAccessibilityServiceTarget(context, shortcutType,
269                         info);
270             case AccessibilityFragmentType.INVISIBLE_TOGGLE:
271                 return new InvisibleToggleAccessibilityServiceTarget(context, shortcutType, info);
272             case AccessibilityFragmentType.TOGGLE:
273                 return new ToggleAccessibilityServiceTarget(context, shortcutType, info);
274             default:
275                 throw new IllegalStateException("Unexpected fragment type");
276         }
277     }
278 
createEnableDialogContentView(Context context, AccessibilityServiceTarget target, View.OnClickListener allowListener, View.OnClickListener denyListener)279     static View createEnableDialogContentView(Context context,
280             AccessibilityServiceTarget target, View.OnClickListener allowListener,
281             View.OnClickListener denyListener) {
282         final LayoutInflater inflater = (LayoutInflater) context.getSystemService(
283                 Context.LAYOUT_INFLATER_SERVICE);
284 
285         final View content = inflater.inflate(
286                 R.layout.accessibility_enable_service_warning, /* root= */ null);
287 
288         final ImageView dialogIcon = content.findViewById(
289                 R.id.accessibility_permissionDialog_icon);
290         dialogIcon.setImageDrawable(target.getIcon());
291 
292         final TextView dialogTitle = content.findViewById(
293                 R.id.accessibility_permissionDialog_title);
294         dialogTitle.setText(context.getString(R.string.accessibility_enable_service_title,
295                 getServiceName(context, target.getLabel())));
296 
297         final Button allowButton = content.findViewById(
298                 R.id.accessibility_permission_enable_allow_button);
299         final Button denyButton = content.findViewById(
300                 R.id.accessibility_permission_enable_deny_button);
301         allowButton.setOnClickListener((view) -> {
302             target.onCheckedChanged(/* isChecked= */ true);
303             allowListener.onClick(view);
304         });
305         denyButton.setOnClickListener((view) -> {
306             target.onCheckedChanged(/* isChecked= */ false);
307             denyListener.onClick(view);
308         });
309 
310         return content;
311     }
312 
313     // Gets the service name and bidi wrap it to protect from bidi side effects.
getServiceName(Context context, CharSequence label)314     private static CharSequence getServiceName(Context context, CharSequence label) {
315         final Locale locale = context.getResources().getConfiguration().getLocales().get(0);
316         return BidiFormatter.getInstance(locale).unicodeWrap(label);
317     }
318 }
319