1 /* 2 * Copyright (C) 2019 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.settings.accessibility; 18 19 import static com.android.internal.accessibility.AccessibilityShortcutController.ACCESSIBILITY_BUTTON_COMPONENT_NAME; 20 import static com.android.internal.accessibility.AccessibilityShortcutController.ACCESSIBILITY_HEARING_AIDS_COMPONENT_NAME; 21 import static com.android.internal.accessibility.AccessibilityShortcutController.MAGNIFICATION_COMPONENT_NAME; 22 23 import android.accessibilityservice.AccessibilityServiceInfo; 24 import android.app.Activity; 25 import android.app.AppOpsManager; 26 import android.app.admin.DevicePolicyManager; 27 import android.app.settings.SettingsEnums; 28 import android.content.ComponentName; 29 import android.content.Intent; 30 import android.content.pm.ResolveInfo; 31 import android.content.pm.ServiceInfo; 32 import android.os.Bundle; 33 import android.os.UserHandle; 34 import android.text.TextUtils; 35 import android.util.Log; 36 import android.view.accessibility.AccessibilityManager; 37 38 import androidx.annotation.NonNull; 39 import androidx.annotation.Nullable; 40 41 import com.android.internal.annotations.VisibleForTesting; 42 import com.android.settings.R; 43 import com.android.settings.core.InstrumentedFragment; 44 import com.android.settings.core.SubSettingLauncher; 45 import com.android.settings.overlay.FeatureFactory; 46 import com.android.settingslib.RestrictedLockUtilsInternal; 47 import com.android.settingslib.accessibility.AccessibilityUtils; 48 49 import java.util.List; 50 import java.util.Objects; 51 import java.util.Set; 52 53 public class AccessibilityDetailsSettingsFragment extends InstrumentedFragment { 54 55 private final static String TAG = "A11yDetailsSettings"; 56 private AppOpsManager mAppOps; 57 58 @Override getMetricsCategory()59 public int getMetricsCategory() { 60 return SettingsEnums.ACCESSIBILITY_DETAILS_SETTINGS; 61 } 62 63 @Override onCreate(Bundle savedInstanceState)64 public void onCreate(Bundle savedInstanceState) { 65 super.onCreate(savedInstanceState); 66 67 mAppOps = getActivity().getSystemService(AppOpsManager.class); 68 69 // In case the Intent doesn't have component name, go to a11y services list. 70 final String extraComponentName = getActivity().getIntent().getStringExtra( 71 Intent.EXTRA_COMPONENT_NAME); 72 if (extraComponentName == null) { 73 Log.w(TAG, "Open accessibility services list due to no component name."); 74 openAccessibilitySettingsAndFinish(); 75 return; 76 } 77 78 final ComponentName componentName = ComponentName.unflattenFromString(extraComponentName); 79 if (openSystemAccessibilitySettingsAndFinish(componentName)) { 80 return; 81 } 82 83 if (openAccessibilityDetailsSettingsAndFinish(componentName)) { 84 return; 85 } 86 // Fall back to open accessibility services list. 87 openAccessibilitySettingsAndFinish(); 88 } 89 openSystemAccessibilitySettingsAndFinish( @ullable ComponentName componentName)90 private boolean openSystemAccessibilitySettingsAndFinish( 91 @Nullable ComponentName componentName) { 92 final LaunchFragmentArguments launchArguments = 93 getSystemAccessibilitySettingsLaunchArguments(componentName); 94 if (launchArguments == null) { 95 return false; 96 } 97 openSubSettings(launchArguments.mDestination, launchArguments.mArguments); 98 finish(); 99 return true; 100 } 101 102 @Nullable getSystemAccessibilitySettingsLaunchArguments( @ullable ComponentName componentName)103 private LaunchFragmentArguments getSystemAccessibilitySettingsLaunchArguments( 104 @Nullable ComponentName componentName) { 105 if (MAGNIFICATION_COMPONENT_NAME.equals(componentName)) { 106 final String destination = ToggleScreenMagnificationPreferenceFragment.class.getName(); 107 return new LaunchFragmentArguments(destination, /* arguments= */ null); 108 } 109 110 if (ACCESSIBILITY_BUTTON_COMPONENT_NAME.equals(componentName)) { 111 final String destination = AccessibilityButtonFragment.class.getName(); 112 return new LaunchFragmentArguments(destination, /* arguments= */ null); 113 } 114 115 if (ACCESSIBILITY_HEARING_AIDS_COMPONENT_NAME.equals(componentName)) { 116 final String destination = AccessibilityHearingAidsFragment.class.getName(); 117 return new LaunchFragmentArguments(destination, /* arguments= */ null); 118 } 119 120 return null; 121 } 122 123 openAccessibilitySettingsAndFinish()124 private void openAccessibilitySettingsAndFinish() { 125 openSubSettings(AccessibilitySettings.class.getName(), /* arguments= */ null); 126 finish(); 127 } 128 openAccessibilityDetailsSettingsAndFinish( @ullable ComponentName componentName)129 private boolean openAccessibilityDetailsSettingsAndFinish( 130 @Nullable ComponentName componentName) { 131 // In case the A11yServiceInfo doesn't exist, go to ally services list. 132 final AccessibilityServiceInfo info = getAccessibilityServiceInfo(componentName); 133 if (info == null) { 134 Log.w(TAG, "openAccessibilityDetailsSettingsAndFinish : invalid component name."); 135 return false; 136 } 137 138 // In case this accessibility service isn't permitted, go to a11y services list. 139 if (!isServiceAllowed(info.getResolveInfo().serviceInfo.applicationInfo.uid, 140 componentName.getPackageName())) { 141 Log.w(TAG, 142 "openAccessibilityDetailsSettingsAndFinish: target accessibility service is" 143 + "prohibited by Device Admin or App Op."); 144 return false; 145 } 146 openSubSettings(ToggleAccessibilityServicePreferenceFragment.class.getName(), 147 buildArguments(info)); 148 finish(); 149 return true; 150 } 151 openSubSettings(@onNull String destination, @Nullable Bundle arguments)152 private void openSubSettings(@NonNull String destination, @Nullable Bundle arguments) { 153 new SubSettingLauncher(getActivity()) 154 .setDestination(destination) 155 .setSourceMetricsCategory(getMetricsCategory()) 156 .setArguments(arguments) 157 .launch(); 158 } 159 160 @VisibleForTesting isServiceAllowed(int uid, String packageName)161 boolean isServiceAllowed(int uid, String packageName) { 162 final DevicePolicyManager dpm = getContext().getSystemService(DevicePolicyManager.class); 163 final List<String> permittedServices = dpm.getPermittedAccessibilityServices( 164 UserHandle.myUserId()); 165 if (permittedServices != null && !permittedServices.contains(packageName)) { 166 return false; 167 } 168 169 return !RestrictedLockUtilsInternal.isEnhancedConfirmationRestricted(getContext(), 170 packageName, AppOpsManager.OPSTR_BIND_ACCESSIBILITY_SERVICE); 171 } 172 getAccessibilityServiceInfo(ComponentName componentName)173 private AccessibilityServiceInfo getAccessibilityServiceInfo(ComponentName componentName) { 174 if (componentName == null) { 175 return null; 176 } 177 178 final List<AccessibilityServiceInfo> serviceInfos = AccessibilityManager.getInstance( 179 getActivity()).getInstalledAccessibilityServiceList(); 180 final int serviceInfoCount = serviceInfos.size(); 181 for (int i = 0; i < serviceInfoCount; i++) { 182 AccessibilityServiceInfo serviceInfo = serviceInfos.get(i); 183 ResolveInfo resolveInfo = serviceInfo.getResolveInfo(); 184 if (componentName.getPackageName().equals(resolveInfo.serviceInfo.packageName) 185 && componentName.getClassName().equals(resolveInfo.serviceInfo.name)) { 186 return serviceInfo; 187 } 188 } 189 return null; 190 } 191 buildArguments(AccessibilityServiceInfo info)192 private Bundle buildArguments(AccessibilityServiceInfo info) { 193 final ResolveInfo resolveInfo = info.getResolveInfo(); 194 final String title = resolveInfo.loadLabel(getActivity().getPackageManager()).toString(); 195 final ServiceInfo serviceInfo = resolveInfo.serviceInfo; 196 final String packageName = serviceInfo.packageName; 197 final ComponentName componentName = new ComponentName(packageName, serviceInfo.name); 198 199 final Set<ComponentName> enabledServices = 200 AccessibilityUtils.getEnabledServicesFromSettings(getActivity()); 201 final boolean serviceEnabled = enabledServices.contains(componentName); 202 String description = info.loadDescription(getActivity().getPackageManager()); 203 204 if (serviceEnabled && info.crashed) { 205 // Update the summaries for services that have crashed. 206 description = getString(R.string.accessibility_description_state_stopped); 207 } 208 209 final Bundle extras = new Bundle(); 210 extras.putString(AccessibilitySettings.EXTRA_PREFERENCE_KEY, 211 componentName.flattenToString()); 212 extras.putBoolean(AccessibilitySettings.EXTRA_CHECKED, serviceEnabled); 213 extras.putString(AccessibilitySettings.EXTRA_TITLE, title); 214 extras.putParcelable(AccessibilitySettings.EXTRA_RESOLVE_INFO, resolveInfo); 215 extras.putString(AccessibilitySettings.EXTRA_SUMMARY, description); 216 217 final String settingsClassName = info.getSettingsActivityName(); 218 if (!TextUtils.isEmpty(settingsClassName)) { 219 extras.putString(AccessibilitySettings.EXTRA_SETTINGS_TITLE, 220 getString(R.string.accessibility_menu_item_settings)); 221 extras.putString(AccessibilitySettings.EXTRA_SETTINGS_COMPONENT_NAME, 222 new ComponentName(packageName, settingsClassName).flattenToString()); 223 } 224 225 final String tileServiceClassName = info.getTileServiceName(); 226 if (!TextUtils.isEmpty(tileServiceClassName)) { 227 extras.putString(AccessibilitySettings.EXTRA_TILE_SERVICE_COMPONENT_NAME, 228 new ComponentName(packageName, tileServiceClassName).flattenToString()); 229 } 230 231 final int metricsCategory = FeatureFactory.getFeatureFactory() 232 .getAccessibilityMetricsFeatureProvider() 233 .getDownloadedFeatureMetricsCategory(componentName); 234 extras.putInt(AccessibilitySettings.EXTRA_METRICS_CATEGORY, metricsCategory); 235 extras.putParcelable(AccessibilitySettings.EXTRA_COMPONENT_NAME, componentName); 236 extras.putInt(AccessibilitySettings.EXTRA_ANIMATED_IMAGE_RES, info.getAnimatedImageRes()); 237 238 final String htmlDescription = info.loadHtmlDescription(getActivity().getPackageManager()); 239 extras.putString(AccessibilitySettings.EXTRA_HTML_DESCRIPTION, htmlDescription); 240 241 final CharSequence intro = info.loadIntro(getActivity().getPackageManager()); 242 extras.putCharSequence(AccessibilitySettings.EXTRA_INTRO, intro); 243 244 // We will log nonA11yTool status from PolicyWarningUIController; others none. 245 extras.putLong(AccessibilitySettings.EXTRA_TIME_FOR_LOGGING, 246 getActivity().getIntent().getLongExtra( 247 AccessibilitySettings.EXTRA_TIME_FOR_LOGGING, 0)); 248 return extras; 249 } 250 finish()251 private void finish() { 252 Activity activity = getActivity(); 253 if (activity == null) { 254 return; 255 } 256 activity.finish(); 257 } 258 259 private static class LaunchFragmentArguments { 260 final String mDestination; 261 final Bundle mArguments; LaunchFragmentArguments(@onNull String destination, @Nullable Bundle arguments)262 LaunchFragmentArguments(@NonNull String destination, @Nullable Bundle arguments) { 263 mDestination = Objects.requireNonNull(destination); 264 mArguments = arguments; 265 } 266 } 267 } 268