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 | */ |
| 56 | final class AccessibilityTargetHelper { |
| 57 | private AccessibilityTargetHelper() {} |
| 58 | |
| 59 | static List<AccessibilityTarget> getTargets(Context context, |
| 60 | @ShortcutType int shortcutType) { |
| 61 | final List<AccessibilityTarget> targets = getInstalledTargets(context, shortcutType); |
| 62 | final AccessibilityManager ams = context.getSystemService(AccessibilityManager.class); |
| 63 | final List<String> requiredTargets = ams.getAccessibilityShortcutTargets(shortcutType); |
| 64 | targets.removeIf(target -> !requiredTargets.contains(target.getId())); |
| 65 | |
| 66 | return targets; |
| 67 | } |
| 68 | |
| 69 | static List<AccessibilityTarget> getInstalledTargets(Context context, |
| 70 | @ShortcutType int shortcutType) { |
| 71 | final List<AccessibilityTarget> targets = new ArrayList<>(); |
| 72 | targets.addAll(getAccessibilityFilteredTargets(context, shortcutType)); |
| 73 | targets.addAll(getWhiteListingFeatureTargets(context, shortcutType)); |
| 74 | |
| 75 | return targets; |
| 76 | } |
| 77 | |
| 78 | private static List<AccessibilityTarget> getAccessibilityFilteredTargets(Context context, |
| 79 | @ShortcutType int shortcutType) { |
| 80 | final List<AccessibilityTarget> serviceTargets = |
| 81 | getAccessibilityServiceTargets(context, shortcutType); |
| 82 | final List<AccessibilityTarget> activityTargets = |
| 83 | getAccessibilityActivityTargets(context, shortcutType); |
| 84 | |
| 85 | for (AccessibilityTarget activityTarget : activityTargets) { |
| 86 | serviceTargets.removeIf( |
| 87 | serviceTarget -> arePackageNameAndLabelTheSame(serviceTarget, activityTarget)); |
| 88 | } |
| 89 | |
| 90 | final List<AccessibilityTarget> targets = new ArrayList<>(); |
| 91 | targets.addAll(serviceTargets); |
| 92 | targets.addAll(activityTargets); |
| 93 | |
| 94 | return targets; |
| 95 | } |
| 96 | |
| 97 | private static boolean arePackageNameAndLabelTheSame(@NonNull AccessibilityTarget serviceTarget, |
| 98 | @NonNull AccessibilityTarget activityTarget) { |
| 99 | final ComponentName serviceComponentName = |
| 100 | ComponentName.unflattenFromString(serviceTarget.getId()); |
| 101 | final ComponentName activityComponentName = |
| 102 | ComponentName.unflattenFromString(activityTarget.getId()); |
| 103 | final boolean isSamePackageName = activityComponentName.getPackageName().equals( |
| 104 | serviceComponentName.getPackageName()); |
| 105 | final boolean isSameLabel = activityTarget.getLabel().equals( |
| 106 | serviceTarget.getLabel()); |
| 107 | |
| 108 | return isSamePackageName && isSameLabel; |
| 109 | } |
| 110 | |
| 111 | private static List<AccessibilityTarget> getAccessibilityServiceTargets(Context context, |
| 112 | @ShortcutType int shortcutType) { |
| 113 | final AccessibilityManager ams = context.getSystemService(AccessibilityManager.class); |
| 114 | final List<AccessibilityServiceInfo> installedServices = |
| 115 | ams.getInstalledAccessibilityServiceList(); |
| 116 | if (installedServices == null) { |
| 117 | return Collections.emptyList(); |
| 118 | } |
| 119 | |
| 120 | final List<AccessibilityTarget> targets = new ArrayList<>(installedServices.size()); |
| 121 | for (AccessibilityServiceInfo info : installedServices) { |
| 122 | final int targetSdk = |
| 123 | info.getResolveInfo().serviceInfo.applicationInfo.targetSdkVersion; |
| 124 | final boolean hasRequestAccessibilityButtonFlag = |
| 125 | (info.flags & AccessibilityServiceInfo.FLAG_REQUEST_ACCESSIBILITY_BUTTON) != 0; |
| 126 | if ((targetSdk <= Build.VERSION_CODES.Q) && !hasRequestAccessibilityButtonFlag |
| 127 | && (shortcutType == ACCESSIBILITY_BUTTON)) { |
| 128 | continue; |
| 129 | } |
| 130 | |
| 131 | targets.add(createAccessibilityServiceTarget(context, shortcutType, info)); |
| 132 | } |
| 133 | |
| 134 | return targets; |
| 135 | } |
| 136 | |
| 137 | private static List<AccessibilityTarget> getAccessibilityActivityTargets(Context context, |
| 138 | @ShortcutType int shortcutType) { |
| 139 | final AccessibilityManager ams = context.getSystemService(AccessibilityManager.class); |
| 140 | final List<AccessibilityShortcutInfo> installedServices = |
| 141 | ams.getInstalledAccessibilityShortcutListAsUser(context, |
| 142 | ActivityManager.getCurrentUser()); |
| 143 | if (installedServices == null) { |
| 144 | return Collections.emptyList(); |
| 145 | } |
| 146 | |
| 147 | final List<AccessibilityTarget> targets = new ArrayList<>(installedServices.size()); |
| 148 | for (AccessibilityShortcutInfo info : installedServices) { |
| 149 | targets.add(new AccessibilityActivityTarget(context, shortcutType, info)); |
| 150 | } |
| 151 | |
| 152 | return targets; |
| 153 | } |
| 154 | |
| 155 | private static List<AccessibilityTarget> getWhiteListingFeatureTargets(Context context, |
| 156 | @ShortcutType int shortcutType) { |
| 157 | final List<AccessibilityTarget> targets = new ArrayList<>(); |
| 158 | |
| 159 | final InvisibleToggleWhiteListingFeatureTarget magnification = |
| 160 | new InvisibleToggleWhiteListingFeatureTarget(context, |
| 161 | shortcutType, |
| 162 | isShortcutContained(context, shortcutType, MAGNIFICATION_CONTROLLER_NAME), |
| 163 | MAGNIFICATION_CONTROLLER_NAME, |
| 164 | context.getString(R.string.accessibility_magnification_chooser_text), |
| 165 | context.getDrawable(R.drawable.ic_accessibility_magnification), |
| 166 | Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED); |
| 167 | |
| 168 | final ToggleWhiteListingFeatureTarget daltonizer = |
| 169 | new ToggleWhiteListingFeatureTarget(context, |
| 170 | shortcutType, |
| 171 | isShortcutContained(context, shortcutType, |
| 172 | DALTONIZER_COMPONENT_NAME.flattenToString()), |
| 173 | DALTONIZER_COMPONENT_NAME.flattenToString(), |
| 174 | context.getString(R.string.color_correction_feature_name), |
| 175 | context.getDrawable(R.drawable.ic_accessibility_color_correction), |
| 176 | Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED); |
| 177 | |
| 178 | final ToggleWhiteListingFeatureTarget colorInversion = |
| 179 | new ToggleWhiteListingFeatureTarget(context, |
| 180 | shortcutType, |
| 181 | isShortcutContained(context, shortcutType, |
| 182 | COLOR_INVERSION_COMPONENT_NAME.flattenToString()), |
| 183 | COLOR_INVERSION_COMPONENT_NAME.flattenToString(), |
| 184 | context.getString(R.string.color_inversion_feature_name), |
| 185 | context.getDrawable(R.drawable.ic_accessibility_color_inversion), |
| 186 | Settings.Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED); |
| 187 | |
| 188 | targets.add(magnification); |
| 189 | targets.add(daltonizer); |
| 190 | targets.add(colorInversion); |
| 191 | |
| 192 | return targets; |
| 193 | } |
| 194 | |
| 195 | private static AccessibilityTarget createAccessibilityServiceTarget(Context context, |
| 196 | @ShortcutType int shortcutType, @NonNull AccessibilityServiceInfo info) { |
| 197 | switch (getAccessibilityServiceFragmentType(info)) { |
| 198 | case AccessibilityFragmentType.VOLUME_SHORTCUT_TOGGLE: |
| 199 | return new VolumeShortcutToggleAccessibilityServiceTarget(context, shortcutType, |
| 200 | info); |
| 201 | case AccessibilityFragmentType.INVISIBLE_TOGGLE: |
| 202 | return new InvisibleToggleAccessibilityServiceTarget(context, shortcutType, info); |
| 203 | case AccessibilityFragmentType.TOGGLE: |
| 204 | return new ToggleAccessibilityServiceTarget(context, shortcutType, info); |
| 205 | default: |
| 206 | throw new IllegalStateException("Unexpected fragment type"); |
| 207 | } |
| 208 | } |
Peter_Liang | fbd4bf7 | 2020-04-08 20:20:15 +0800 | [diff] [blame] | 209 | |
| 210 | static View createEnableDialogContentView(Context context, |
| 211 | AccessibilityServiceTarget target, View.OnClickListener allowListener, |
| 212 | View.OnClickListener denyListener) { |
| 213 | final LayoutInflater inflater = (LayoutInflater) context.getSystemService( |
| 214 | Context.LAYOUT_INFLATER_SERVICE); |
| 215 | |
| 216 | final View content = inflater.inflate( |
| 217 | R.layout.accessibility_enable_service_encryption_warning, /* root= */ null); |
| 218 | |
| 219 | final TextView encryptionWarningView = (TextView) content.findViewById( |
| 220 | R.id.accessibility_encryption_warning); |
| 221 | if (StorageManager.isNonDefaultBlockEncrypted()) { |
| 222 | final String text = context.getString( |
| 223 | R.string.accessibility_enable_service_encryption_warning, |
| 224 | getServiceName(context, target.getLabel())); |
| 225 | encryptionWarningView.setText(text); |
| 226 | encryptionWarningView.setVisibility(View.VISIBLE); |
| 227 | } else { |
| 228 | encryptionWarningView.setVisibility(View.GONE); |
| 229 | } |
| 230 | |
| 231 | final ImageView dialogIcon = content.findViewById( |
| 232 | R.id.accessibility_permissionDialog_icon); |
| 233 | dialogIcon.setImageDrawable(target.getIcon()); |
| 234 | |
| 235 | final TextView dialogTitle = content.findViewById( |
| 236 | R.id.accessibility_permissionDialog_title); |
| 237 | dialogTitle.setText(context.getString(R.string.accessibility_enable_service_title, |
| 238 | getServiceName(context, target.getLabel()))); |
| 239 | |
| 240 | final Button allowButton = content.findViewById( |
| 241 | R.id.accessibility_permission_enable_allow_button); |
| 242 | final Button denyButton = content.findViewById( |
| 243 | R.id.accessibility_permission_enable_deny_button); |
| 244 | allowButton.setOnClickListener((view) -> { |
| 245 | target.onCheckedChanged(/* isChecked= */ true); |
| 246 | allowListener.onClick(view); |
| 247 | }); |
| 248 | denyButton.setOnClickListener((view) -> { |
| 249 | target.onCheckedChanged(/* isChecked= */ false); |
| 250 | denyListener.onClick(view); |
| 251 | }); |
| 252 | |
| 253 | return content; |
| 254 | } |
| 255 | |
| 256 | // Gets the service name and bidi wrap it to protect from bidi side effects. |
| 257 | private static CharSequence getServiceName(Context context, CharSequence label) { |
| 258 | final Locale locale = context.getResources().getConfiguration().getLocales().get(0); |
| 259 | return BidiFormatter.getInstance(locale).unicodeWrap(label); |
| 260 | } |
Peter_Liang | 7d9af05 | 2020-04-08 20:16:25 +0800 | [diff] [blame] | 261 | } |