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