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