1/* 2 * Copyright (C) 2009 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 17package com.android.settings; 18 19import android.app.AlertDialog; 20import android.app.Service; 21import android.content.DialogInterface; 22import android.content.Intent; 23import android.content.pm.ApplicationInfo; 24import android.content.pm.PackageManager; 25import android.content.pm.PackageManager.NameNotFoundException; 26import android.content.pm.ServiceInfo; 27import android.net.Uri; 28import android.os.Bundle; 29import android.os.SystemProperties; 30import android.preference.CheckBoxPreference; 31import android.preference.Preference; 32import android.preference.PreferenceActivity; 33import android.preference.PreferenceCategory; 34import android.preference.PreferenceGroup; 35import android.preference.PreferenceScreen; 36import android.provider.Settings; 37import android.text.TextUtils; 38import android.view.KeyCharacterMap; 39import android.view.KeyEvent; 40import android.view.accessibility.AccessibilityManager; 41 42import java.util.HashSet; 43import java.util.LinkedHashMap; 44import java.util.List; 45import java.util.Map; 46 47/** 48 * Activity with the accessibility settings. 49 */ 50public class AccessibilitySettings extends PreferenceActivity { 51 private static final String DEFAULT_SCREENREADER_MARKET_LINK = 52 "market://search?q=pname:com.google.android.marvin.talkback"; 53 54 private final String TOGGLE_ACCESSIBILITY_SERVICE_CHECKBOX = 55 "toggle_accessibility_service_checkbox"; 56 57 private static final String ACCESSIBILITY_SERVICES_CATEGORY = 58 "accessibility_services_category"; 59 60 private static final String POWER_BUTTON_CATEGORY = 61 "power_button_category"; 62 63 private final String POWER_BUTTON_ENDS_CALL_CHECKBOX = 64 "power_button_ends_call"; 65 66 private CheckBoxPreference mToggleCheckBox; 67 68 private PreferenceCategory mPowerButtonCategory; 69 private CheckBoxPreference mPowerButtonEndsCallCheckBox; 70 71 private Map<String, ServiceInfo> mAccessibilityServices = 72 new LinkedHashMap<String, ServiceInfo>(); 73 74 private TextUtils.SimpleStringSplitter mStringColonSplitter = 75 new TextUtils.SimpleStringSplitter(':'); 76 77 private PreferenceGroup mAccessibilityServicesCategory; 78 79 @Override 80 protected void onCreate(Bundle icicle) { 81 super.onCreate(icicle); 82 addPreferencesFromResource(R.xml.accessibility_settings); 83 84 mToggleCheckBox = (CheckBoxPreference) findPreference( 85 TOGGLE_ACCESSIBILITY_SERVICE_CHECKBOX); 86 87 mPowerButtonCategory = (PreferenceCategory) findPreference(POWER_BUTTON_CATEGORY); 88 mPowerButtonEndsCallCheckBox = (CheckBoxPreference) findPreference( 89 POWER_BUTTON_ENDS_CALL_CHECKBOX); 90 91 addAccessibilitServicePreferences(); 92 } 93 94 @Override 95 protected void onResume() { 96 super.onResume(); 97 98 final HashSet<String> enabled = new HashSet<String>(); 99 String settingValue = Settings.Secure.getString(getContentResolver(), 100 Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES); 101 if (settingValue != null) { 102 TextUtils.SimpleStringSplitter splitter = mStringColonSplitter; 103 splitter.setString(settingValue); 104 while (splitter.hasNext()) { 105 enabled.add(splitter.next()); 106 } 107 } 108 109 Map<String, ServiceInfo> accessibilityServices = mAccessibilityServices; 110 111 for (String key : accessibilityServices.keySet()) { 112 CheckBoxPreference preference = (CheckBoxPreference) findPreference(key); 113 if (preference != null) { 114 preference.setChecked(enabled.contains(key)); 115 } 116 } 117 118 int serviceState = Settings.Secure.getInt(getContentResolver(), 119 Settings.Secure.ACCESSIBILITY_ENABLED, 0); 120 121 if (!accessibilityServices.isEmpty()) { 122 if (serviceState == 1) { 123 mToggleCheckBox.setChecked(true); 124 } else { 125 setAccessibilityServicePreferencesState(false); 126 } 127 mToggleCheckBox.setEnabled(true); 128 } else { 129 if (serviceState == 1) { 130 // no service and accessibility is enabled => disable 131 Settings.Secure.putInt(getContentResolver(), 132 Settings.Secure.ACCESSIBILITY_ENABLED, 0); 133 } 134 mToggleCheckBox.setEnabled(false); 135 // Notify user that they do not have any accessibility apps 136 // installed and direct them to Market to get TalkBack 137 displayNoAppsAlert(); 138 } 139 140 if (KeyCharacterMap.deviceHasKey(KeyEvent.KEYCODE_POWER)) { 141 int incallPowerBehavior = Settings.Secure.getInt(getContentResolver(), 142 Settings.Secure.INCALL_POWER_BUTTON_BEHAVIOR, 143 Settings.Secure.INCALL_POWER_BUTTON_BEHAVIOR_DEFAULT); 144 // The checkbox is labeled "Power button ends call"; thus the in-call 145 // Power button behavior is INCALL_POWER_BUTTON_BEHAVIOR_HANGUP if 146 // checked, and INCALL_POWER_BUTTON_BEHAVIOR_SCREEN_OFF if unchecked. 147 boolean powerButtonCheckboxEnabled = 148 (incallPowerBehavior == Settings.Secure.INCALL_POWER_BUTTON_BEHAVIOR_HANGUP); 149 mPowerButtonEndsCallCheckBox.setChecked(powerButtonCheckboxEnabled); 150 mPowerButtonEndsCallCheckBox.setEnabled(true); 151 } else { 152 // No POWER key on the current device; this entire category is irrelevant. 153 getPreferenceScreen().removePreference(mPowerButtonCategory); 154 } 155 } 156 157 @Override 158 protected void onPause() { 159 super.onPause(); 160 161 persistEnabledAccessibilityServices(); 162 } 163 164 /** 165 * Sets the state of the preferences for enabling/disabling 166 * AccessibilityServices. 167 * 168 * @param isEnabled If to enable or disable the preferences. 169 */ 170 private void setAccessibilityServicePreferencesState(boolean isEnabled) { 171 if (mAccessibilityServicesCategory == null) { 172 return; 173 } 174 175 int count = mAccessibilityServicesCategory.getPreferenceCount(); 176 for (int i = 0; i < count; i++) { 177 Preference pref = mAccessibilityServicesCategory.getPreference(i); 178 pref.setEnabled(isEnabled); 179 } 180 } 181 182 @Override 183 public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) { 184 final String key = preference.getKey(); 185 186 if (TOGGLE_ACCESSIBILITY_SERVICE_CHECKBOX.equals(key)) { 187 boolean isChecked = ((CheckBoxPreference) preference).isChecked(); 188 handleEnableAccessibilityStateChange((CheckBoxPreference) preference); 189 } else if (POWER_BUTTON_ENDS_CALL_CHECKBOX.equals(key)) { 190 boolean isChecked = ((CheckBoxPreference) preference).isChecked(); 191 // The checkbox is labeled "Power button ends call"; thus the in-call 192 // Power button behavior is INCALL_POWER_BUTTON_BEHAVIOR_HANGUP if 193 // checked, and INCALL_POWER_BUTTON_BEHAVIOR_SCREEN_OFF if unchecked. 194 Settings.Secure.putInt(getContentResolver(), 195 Settings.Secure.INCALL_POWER_BUTTON_BEHAVIOR, 196 (isChecked ? Settings.Secure.INCALL_POWER_BUTTON_BEHAVIOR_HANGUP 197 : Settings.Secure.INCALL_POWER_BUTTON_BEHAVIOR_SCREEN_OFF)); 198 } else if (preference instanceof CheckBoxPreference) { 199 handleEnableAccessibilityServiceStateChange((CheckBoxPreference) preference); 200 } 201 202 return super.onPreferenceTreeClick(preferenceScreen, preference); 203 } 204 205 /** 206 * Handles the change of the accessibility enabled setting state. 207 * 208 * @param preference The preference for enabling/disabling accessibility. 209 */ 210 private void handleEnableAccessibilityStateChange(CheckBoxPreference preference) { 211 if (preference.isChecked()) { 212 Settings.Secure.putInt(getContentResolver(), 213 Settings.Secure.ACCESSIBILITY_ENABLED, 1); 214 setAccessibilityServicePreferencesState(true); 215 } else { 216 final CheckBoxPreference checkBoxPreference = preference; 217 AlertDialog dialog = (new AlertDialog.Builder(this)) 218 .setTitle(android.R.string.dialog_alert_title) 219 .setIcon(android.R.drawable.ic_dialog_alert) 220 .setMessage(getString(R.string.accessibility_service_disable_warning)) 221 .setCancelable(true) 222 .setPositiveButton(android.R.string.ok, 223 new DialogInterface.OnClickListener() { 224 public void onClick(DialogInterface dialog, int which) { 225 Settings.Secure.putInt(getContentResolver(), 226 Settings.Secure.ACCESSIBILITY_ENABLED, 0); 227 setAccessibilityServicePreferencesState(false); 228 } 229 }) 230 .setNegativeButton(android.R.string.cancel, 231 new DialogInterface.OnClickListener() { 232 public void onClick(DialogInterface dialog, int which) { 233 checkBoxPreference.setChecked(true); 234 } 235 }) 236 .create(); 237 dialog.show(); 238 } 239 } 240 241 /** 242 * Handles the change of the preference for enabling/disabling an AccessibilityService. 243 * 244 * @param preference The preference. 245 */ 246 private void handleEnableAccessibilityServiceStateChange(CheckBoxPreference preference) { 247 if (preference.isChecked()) { 248 final CheckBoxPreference checkBoxPreference = preference; 249 AlertDialog dialog = (new AlertDialog.Builder(this)) 250 .setTitle(android.R.string.dialog_alert_title) 251 .setIcon(android.R.drawable.ic_dialog_alert) 252 .setMessage(getString(R.string.accessibility_service_security_warning, 253 mAccessibilityServices.get(preference.getKey()) 254 .applicationInfo.loadLabel(getPackageManager()))) 255 .setCancelable(true) 256 .setPositiveButton(android.R.string.ok, 257 new DialogInterface.OnClickListener() { 258 public void onClick(DialogInterface dialog, int which) { 259 checkBoxPreference.setChecked(true); 260 persistEnabledAccessibilityServices(); 261 } 262 }) 263 .setNegativeButton(android.R.string.cancel, 264 new DialogInterface.OnClickListener() { 265 public void onClick(DialogInterface dialog, int which) { 266 checkBoxPreference.setChecked(false); 267 } 268 }) 269 .create(); 270 dialog.show(); 271 } else { 272 persistEnabledAccessibilityServices(); 273 } 274 } 275 276 /** 277 * Persists the Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES setting. 278 * The AccessibilityManagerService watches this property and manages the 279 * AccessibilityServices. 280 */ 281 private void persistEnabledAccessibilityServices() { 282 StringBuilder builder = new StringBuilder(256); 283 284 int firstEnabled = -1; 285 for (String key : mAccessibilityServices.keySet()) { 286 CheckBoxPreference preference = (CheckBoxPreference) findPreference(key); 287 if (preference.isChecked()) { 288 builder.append(key); 289 builder.append(':'); 290 } 291 } 292 293 Settings.Secure.putString(getContentResolver(), 294 Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, builder.toString()); 295 } 296 297 /** 298 * Adds {@link CheckBoxPreference} for enabling or disabling an accessibility services. 299 */ 300 private void addAccessibilitServicePreferences() { 301 AccessibilityManager accessibilityManager = 302 (AccessibilityManager) getSystemService(Service.ACCESSIBILITY_SERVICE); 303 304 List<ServiceInfo> installedServices = accessibilityManager.getAccessibilityServiceList(); 305 306 mAccessibilityServicesCategory = 307 (PreferenceGroup) findPreference(ACCESSIBILITY_SERVICES_CATEGORY); 308 309 if (installedServices.isEmpty()) { 310 getPreferenceScreen().removePreference(mAccessibilityServicesCategory); 311 mAccessibilityServicesCategory = null; 312 return; 313 } 314 315 for (int i = 0, count = installedServices.size(); i < count; ++i) { 316 ServiceInfo serviceInfo = installedServices.get(i); 317 String key = serviceInfo.packageName + "/" + serviceInfo.name; 318 319 mAccessibilityServices.put(key, serviceInfo); 320 321 CheckBoxPreference preference = new CheckBoxPreference(this); 322 preference.setKey(key); 323 preference.setTitle(serviceInfo.loadLabel(getPackageManager())); 324 mAccessibilityServicesCategory.addPreference(preference); 325 } 326 } 327 328 /** 329 * Displays a message telling the user that they do not have any accessibility 330 * related apps installed and that they can get TalkBack (Google's free screen 331 * reader) from Market. 332 */ 333 private void displayNoAppsAlert() { 334 try { 335 PackageManager pm = getPackageManager(); 336 ApplicationInfo info = pm.getApplicationInfo("com.android.vending", 0); 337 } catch (NameNotFoundException e) { 338 // This is a no-op if the user does not have Android Market 339 return; 340 } 341 AlertDialog.Builder noAppsAlert = new AlertDialog.Builder(this); 342 noAppsAlert.setTitle(R.string.accessibility_service_no_apps_title); 343 noAppsAlert.setMessage(R.string.accessibility_service_no_apps_message); 344 345 noAppsAlert.setPositiveButton(android.R.string.ok, 346 new DialogInterface.OnClickListener() { 347 public void onClick(DialogInterface dialog, int which) { 348 String screenreaderMarketLink = 349 SystemProperties.get("ro.screenreader.market", 350 DEFAULT_SCREENREADER_MARKET_LINK); 351 Uri marketUri = Uri.parse(screenreaderMarketLink); 352 Intent marketIntent = new Intent(Intent.ACTION_VIEW, marketUri); 353 startActivity(marketIntent); 354 finish(); 355 } 356 }); 357 358 noAppsAlert.setNegativeButton(android.R.string.cancel, 359 new DialogInterface.OnClickListener() { 360 public void onClick(DialogInterface dialog, int which) { 361 } 362 }); 363 364 noAppsAlert.show(); 365 } 366} 367