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