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