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 
29 import android.accessibilityservice.AccessibilityServiceInfo;
30 import android.accessibilityservice.AccessibilityShortcutInfo;
31 import android.annotation.NonNull;
32 import android.app.ActivityManager;
33 import android.content.ComponentName;
34 import android.content.Context;
35 import android.os.Build;
36 import android.os.storage.StorageManager;
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 
214         final ToggleAllowListingFeatureTarget daltonizer =
215                 new ToggleAllowListingFeatureTarget(context,
216                         shortcutType,
217                         isShortcutContained(context, shortcutType,
218                                 DALTONIZER_COMPONENT_NAME.flattenToString()),
219                         DALTONIZER_COMPONENT_NAME.flattenToString(),
220                         context.getString(R.string.color_correction_feature_name),
221                         context.getDrawable(R.drawable.ic_accessibility_color_correction),
222                         Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED);
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                         context.getString(R.string.color_inversion_feature_name),
231                         context.getDrawable(R.drawable.ic_accessibility_color_inversion),
232                         Settings.Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED);
233 
234         final ToggleAllowListingFeatureTarget oneHandedMode =
235                 new ToggleAllowListingFeatureTarget(context,
236                         shortcutType,
237                         isShortcutContained(context, shortcutType,
238                                 ONE_HANDED_COMPONENT_NAME.flattenToString()),
239                         ONE_HANDED_COMPONENT_NAME.flattenToString(),
240                         context.getString(R.string.one_handed_mode_feature_name),
241                         context.getDrawable(R.drawable.ic_accessibility_one_handed),
242                         Settings.Secure.ONE_HANDED_MODE_ACTIVATED);
243 
244         final ToggleAllowListingFeatureTarget reduceBrightColors =
245                 new ToggleAllowListingFeatureTarget(context,
246                         shortcutType,
247                         isShortcutContained(context, shortcutType,
248                                 REDUCE_BRIGHT_COLORS_COMPONENT_NAME.flattenToString()),
249                         REDUCE_BRIGHT_COLORS_COMPONENT_NAME.flattenToString(),
250                         context.getString(R.string.reduce_bright_colors_feature_name),
251                         context.getDrawable(R.drawable.ic_accessibility_reduce_bright_colors),
252                         Settings.Secure.REDUCE_BRIGHT_COLORS_ACTIVATED);
253 
254         targets.add(magnification);
255         targets.add(daltonizer);
256         targets.add(colorInversion);
257         targets.add(oneHandedMode);
258         targets.add(reduceBrightColors);
259 
260         return targets;
261     }
262 
createAccessibilityServiceTarget(Context context, @ShortcutType int shortcutType, @NonNull AccessibilityServiceInfo info)263     private static AccessibilityTarget createAccessibilityServiceTarget(Context context,
264             @ShortcutType int shortcutType, @NonNull AccessibilityServiceInfo info) {
265         switch (getAccessibilityServiceFragmentType(info)) {
266             case AccessibilityFragmentType.VOLUME_SHORTCUT_TOGGLE:
267                 return new VolumeShortcutToggleAccessibilityServiceTarget(context, shortcutType,
268                         info);
269             case AccessibilityFragmentType.INVISIBLE_TOGGLE:
270                 return new InvisibleToggleAccessibilityServiceTarget(context, shortcutType, info);
271             case AccessibilityFragmentType.TOGGLE:
272                 return new ToggleAccessibilityServiceTarget(context, shortcutType, info);
273             default:
274                 throw new IllegalStateException("Unexpected fragment type");
275         }
276     }
277 
createEnableDialogContentView(Context context, AccessibilityServiceTarget target, View.OnClickListener allowListener, View.OnClickListener denyListener)278     static View createEnableDialogContentView(Context context,
279             AccessibilityServiceTarget target, View.OnClickListener allowListener,
280             View.OnClickListener denyListener) {
281         final LayoutInflater inflater = (LayoutInflater) context.getSystemService(
282                 Context.LAYOUT_INFLATER_SERVICE);
283 
284         final View content = inflater.inflate(
285                 R.layout.accessibility_enable_service_encryption_warning, /* root= */ null);
286 
287         final TextView encryptionWarningView = (TextView) content.findViewById(
288                 R.id.accessibility_encryption_warning);
289         if (StorageManager.isNonDefaultBlockEncrypted()) {
290             final String text = context.getString(
291                     R.string.accessibility_enable_service_encryption_warning,
292                     getServiceName(context, target.getLabel()));
293             encryptionWarningView.setText(text);
294             encryptionWarningView.setVisibility(View.VISIBLE);
295         } else {
296             encryptionWarningView.setVisibility(View.GONE);
297         }
298 
299         final ImageView dialogIcon = content.findViewById(
300                 R.id.accessibility_permissionDialog_icon);
301         dialogIcon.setImageDrawable(target.getIcon());
302 
303         final TextView dialogTitle = content.findViewById(
304                 R.id.accessibility_permissionDialog_title);
305         dialogTitle.setText(context.getString(R.string.accessibility_enable_service_title,
306                 getServiceName(context, target.getLabel())));
307 
308         final Button allowButton = content.findViewById(
309                 R.id.accessibility_permission_enable_allow_button);
310         final Button denyButton = content.findViewById(
311                 R.id.accessibility_permission_enable_deny_button);
312         allowButton.setOnClickListener((view) -> {
313             target.onCheckedChanged(/* isChecked= */ true);
314             allowListener.onClick(view);
315         });
316         denyButton.setOnClickListener((view) -> {
317             target.onCheckedChanged(/* isChecked= */ false);
318             denyListener.onClick(view);
319         });
320 
321         return content;
322     }
323 
324     // Gets the service name and bidi wrap it to protect from bidi side effects.
getServiceName(Context context, CharSequence label)325     private static CharSequence getServiceName(Context context, CharSequence label) {
326         final Locale locale = context.getResources().getConfiguration().getLocales().get(0);
327         return BidiFormatter.getInstance(locale).unicodeWrap(label);
328     }
329 }
330