1 /* 2 * Copyright (C) 2021 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.tv.settings.library.settingslib; 18 19 import android.accessibilityservice.AccessibilityServiceInfo; 20 import android.content.ComponentName; 21 import android.content.Context; 22 import android.content.pm.ResolveInfo; 23 import android.content.pm.ServiceInfo; 24 import android.content.res.Configuration; 25 import android.content.res.Resources; 26 import android.os.UserHandle; 27 import android.provider.Settings; 28 import android.text.TextUtils; 29 import android.util.ArraySet; 30 import android.view.accessibility.AccessibilityManager; 31 32 import java.util.Collections; 33 import java.util.HashSet; 34 import java.util.List; 35 import java.util.Locale; 36 import java.util.Set; 37 38 public class AccessibilityUtils { 39 public static final char ENABLED_ACCESSIBILITY_SERVICES_SEPARATOR = ':'; 40 41 /** 42 * @return the set of enabled accessibility services. If there are no services, 43 * it returns the unmodifiable {@link Collections#emptySet()}. 44 */ getEnabledServicesFromSettings(Context context)45 public static Set<ComponentName> getEnabledServicesFromSettings(Context context) { 46 return getEnabledServicesFromSettings(context, UserHandle.myUserId()); 47 } 48 49 /** 50 * Check if the accessibility service is crashed 51 * 52 * @param packageName The package name to check 53 * @param serviceName The service name to check 54 * @param installedServiceInfos The list of installed accessibility service 55 * @return {@code true} if the accessibility service is crashed for the user. 56 * {@code false} otherwise. 57 */ hasServiceCrashed(String packageName, String serviceName, List<AccessibilityServiceInfo> installedServiceInfos)58 public static boolean hasServiceCrashed(String packageName, String serviceName, 59 List<AccessibilityServiceInfo> installedServiceInfos) { 60 for (int i = 0; i < installedServiceInfos.size(); i++) { 61 final AccessibilityServiceInfo accessibilityServiceInfo = installedServiceInfos.get(i); 62 final ServiceInfo serviceInfo = 63 installedServiceInfos.get(i).getResolveInfo().serviceInfo; 64 if (TextUtils.equals(serviceInfo.packageName, packageName) 65 && TextUtils.equals(serviceInfo.name, serviceName)) { 66 return accessibilityServiceInfo.crashed; 67 } 68 } 69 return false; 70 } 71 72 /** 73 * @return the set of enabled accessibility services for {@param userId}. If there are no 74 * services, it returns the unmodifiable {@link Collections#emptySet()}. 75 */ getEnabledServicesFromSettings(Context context, int userId)76 public static Set<ComponentName> getEnabledServicesFromSettings(Context context, int userId) { 77 final String enabledServicesSetting = Settings.Secure.getStringForUser( 78 context.getContentResolver(), Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, 79 userId); 80 if (TextUtils.isEmpty(enabledServicesSetting)) { 81 return Collections.emptySet(); 82 } 83 84 final Set<ComponentName> enabledServices = new HashSet<>(); 85 final TextUtils.StringSplitter colonSplitter = 86 new TextUtils.SimpleStringSplitter(ENABLED_ACCESSIBILITY_SERVICES_SEPARATOR); 87 colonSplitter.setString(enabledServicesSetting); 88 89 for (String componentNameString : colonSplitter) { 90 final ComponentName enabledService = ComponentName.unflattenFromString( 91 componentNameString); 92 if (enabledService != null) { 93 enabledServices.add(enabledService); 94 } 95 } 96 97 return enabledServices; 98 } 99 100 /** 101 * @return a localized version of the text resource specified by resId 102 */ getTextForLocale(Context context, Locale locale, int resId)103 public static CharSequence getTextForLocale(Context context, Locale locale, int resId) { 104 final Resources res = context.getResources(); 105 final Configuration config = new Configuration(res.getConfiguration()); 106 config.setLocale(locale); 107 final Context langContext = context.createConfigurationContext(config); 108 return langContext.getText(resId); 109 } 110 111 /** 112 * Changes an accessibility component's state. 113 */ setAccessibilityServiceState(Context context, ComponentName toggledService, boolean enabled)114 public static void setAccessibilityServiceState(Context context, ComponentName toggledService, 115 boolean enabled) { 116 setAccessibilityServiceState(context, toggledService, enabled, UserHandle.myUserId()); 117 } 118 119 /** 120 * Changes an accessibility component's state for {@param userId}. 121 */ setAccessibilityServiceState(Context context, ComponentName toggledService, boolean enabled, int userId)122 public static void setAccessibilityServiceState(Context context, ComponentName toggledService, 123 boolean enabled, int userId) { 124 // Parse the enabled services. 125 Set<ComponentName> enabledServices = AccessibilityUtils.getEnabledServicesFromSettings( 126 context, userId); 127 128 if (enabledServices.isEmpty()) { 129 enabledServices = new ArraySet<>(1); 130 } 131 132 // Determine enabled services and accessibility state. 133 boolean accessibilityEnabled = false; 134 if (enabled) { 135 enabledServices.add(toggledService); 136 // Enabling at least one service enables accessibility. 137 accessibilityEnabled = true; 138 } else { 139 enabledServices.remove(toggledService); 140 // Check how many enabled and installed services are present. 141 Set<ComponentName> installedServices = getInstalledServices(context); 142 for (ComponentName enabledService : enabledServices) { 143 if (installedServices.contains(enabledService)) { 144 // Disabling the last service disables accessibility. 145 accessibilityEnabled = true; 146 break; 147 } 148 } 149 } 150 151 // Update the enabled services setting. 152 StringBuilder enabledServicesBuilder = new StringBuilder(); 153 // Keep the enabled services even if they are not installed since we 154 // have no way to know whether the application restore process has 155 // completed. In general the system should be responsible for the 156 // clean up not settings. 157 for (ComponentName enabledService : enabledServices) { 158 enabledServicesBuilder.append(enabledService.flattenToString()); 159 enabledServicesBuilder.append( 160 AccessibilityUtils.ENABLED_ACCESSIBILITY_SERVICES_SEPARATOR); 161 } 162 final int enabledServicesBuilderLength = enabledServicesBuilder.length(); 163 if (enabledServicesBuilderLength > 0) { 164 enabledServicesBuilder.deleteCharAt(enabledServicesBuilderLength - 1); 165 } 166 Settings.Secure.putStringForUser(context.getContentResolver(), 167 Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, 168 enabledServicesBuilder.toString(), userId); 169 } 170 171 /** 172 * Get the name of the service that should be toggled by the accessibility shortcut. Use 173 * an OEM-configurable default if the setting has never been set. 174 * 175 * @param context A valid context 176 * @param userId The user whose settings should be checked 177 * @return The component name, flattened to a string, of the target service. 178 */ getShortcutTargetServiceComponentNameString( Context context, int userId)179 public static String getShortcutTargetServiceComponentNameString( 180 Context context, int userId) { 181 final String currentShortcutServiceId = Settings.Secure.getStringForUser( 182 context.getContentResolver(), Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE, 183 userId); 184 if (currentShortcutServiceId != null) { 185 return currentShortcutServiceId; 186 } 187 return context.getString( 188 context.getResources().getIdentifier("config_defaultAccessibilityService", 189 "string", "android")); 190 } 191 getInstalledServices(Context context)192 private static Set<ComponentName> getInstalledServices(Context context) { 193 final Set<ComponentName> installedServices = new HashSet<>(); 194 installedServices.clear(); 195 196 final List<AccessibilityServiceInfo> installedServiceInfos = 197 AccessibilityManager.getInstance(context) 198 .getInstalledAccessibilityServiceList(); 199 if (installedServiceInfos == null) { 200 return installedServices; 201 } 202 203 for (final AccessibilityServiceInfo info : installedServiceInfos) { 204 final ResolveInfo resolveInfo = info.getResolveInfo(); 205 final ComponentName installedService = new ComponentName( 206 resolveInfo.serviceInfo.packageName, 207 resolveInfo.serviceInfo.name); 208 installedServices.add(installedService); 209 } 210 return installedServices; 211 } 212 213 } 214