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.accessibilityservice.AccessibilityServiceInfo; 20import android.app.ActionBar; 21import android.app.Activity; 22import android.app.ActivityManagerNative; 23import android.app.AlertDialog; 24import android.app.Dialog; 25import android.content.ComponentName; 26import android.content.Context; 27import android.content.DialogInterface; 28import android.content.Intent; 29import android.content.SharedPreferences; 30import android.content.pm.ResolveInfo; 31import android.content.pm.ServiceInfo; 32import android.content.res.Configuration; 33import android.net.Uri; 34import android.os.Bundle; 35import android.os.Handler; 36import android.os.Message; 37import android.os.RemoteException; 38import android.os.ServiceManager; 39import android.os.SystemProperties; 40import android.preference.CheckBoxPreference; 41import android.preference.ListPreference; 42import android.preference.Preference; 43import android.preference.PreferenceActivity; 44import android.preference.PreferenceCategory; 45import android.preference.PreferenceScreen; 46import android.provider.Settings; 47import android.text.TextUtils; 48import android.text.TextUtils.SimpleStringSplitter; 49import android.util.Log; 50import android.view.Gravity; 51import android.view.IWindowManager; 52import android.view.KeyCharacterMap; 53import android.view.KeyEvent; 54import android.view.Menu; 55import android.view.MenuInflater; 56import android.view.MenuItem; 57import android.view.Surface; 58import android.view.View; 59import android.view.accessibility.AccessibilityEvent; 60import android.view.accessibility.AccessibilityManager; 61import android.widget.LinearLayout; 62import android.widget.Switch; 63import android.widget.TextView; 64 65import com.android.internal.content.PackageMonitor; 66import com.android.settings.AccessibilitySettings.ToggleSwitch.OnBeforeCheckedChangeListener; 67 68import java.util.HashMap; 69import java.util.HashSet; 70import java.util.List; 71import java.util.Map; 72import java.util.Set; 73 74/** 75 * Activity with the accessibility settings. 76 */ 77public class AccessibilitySettings extends SettingsPreferenceFragment implements DialogCreatable, 78 Preference.OnPreferenceChangeListener { 79 private static final String TAG = "AccessibilitySettings"; 80 81 private static final String DEFAULT_SCREENREADER_MARKET_LINK = 82 "market://search?q=pname:com.google.android.marvin.talkback"; 83 84 private static final float LARGE_FONT_SCALE = 1.3f; 85 86 private static final String SYSTEM_PROPERTY_MARKET_URL = "ro.screenreader.market"; 87 88 // Timeout before we update the services if packages are added/removed since 89 // the AccessibilityManagerService has to do that processing first to generate 90 // the AccessibilityServiceInfo we need for proper presentation. 91 private static final long DELAY_UPDATE_SERVICES_MILLIS = 1000; 92 93 private static final char ENABLED_ACCESSIBILITY_SERVICES_SEPARATOR = ':'; 94 95 private static final String KEY_ACCESSIBILITY_TUTORIAL_LAUNCHED_ONCE = 96 "key_accessibility_tutorial_launched_once"; 97 98 private static final String KEY_INSTALL_ACCESSIBILITY_SERVICE_OFFERED_ONCE = 99 "key_install_accessibility_service_offered_once"; 100 101 // Preference categories 102 private static final String SERVICES_CATEGORY = "services_category"; 103 private static final String SYSTEM_CATEGORY = "system_category"; 104 105 // Preferences 106 private static final String TOGGLE_LARGE_TEXT_PREFERENCE = "toggle_large_text_preference"; 107 private static final String TOGGLE_POWER_BUTTON_ENDS_CALL_PREFERENCE = 108 "toggle_power_button_ends_call_preference"; 109 private static final String TOGGLE_AUTO_ROTATE_SCREEN_PREFERENCE = 110 "toggle_auto_rotate_screen_preference"; 111 private static final String TOGGLE_SPEAK_PASSWORD_PREFERENCE = 112 "toggle_speak_password_preference"; 113 private static final String TOGGLE_TOUCH_EXPLORATION_PREFERENCE = 114 "toggle_touch_exploration_preference"; 115 private static final String SELECT_LONG_PRESS_TIMEOUT_PREFERENCE = 116 "select_long_press_timeout_preference"; 117 private static final String TOGGLE_SCRIPT_INJECTION_PREFERENCE = 118 "toggle_script_injection_preference"; 119 120 // Extras passed to sub-fragments. 121 private static final String EXTRA_PREFERENCE_KEY = "preference_key"; 122 private static final String EXTRA_CHECKED = "checked"; 123 private static final String EXTRA_TITLE = "title"; 124 private static final String EXTRA_SUMMARY = "summary"; 125 private static final String EXTRA_ENABLE_WARNING_TITLE = "enable_warning_title"; 126 private static final String EXTRA_ENABLE_WARNING_MESSAGE = "enable_warning_message"; 127 private static final String EXTRA_DISABLE_WARNING_TITLE = "disable_warning_title"; 128 private static final String EXTRA_DISABLE_WARNING_MESSAGE = "disable_warning_message"; 129 private static final String EXTRA_SETTINGS_TITLE = "settings_title"; 130 private static final String EXTRA_SETTINGS_COMPONENT_NAME = "settings_component_name"; 131 132 // Dialog IDs. 133 private static final int DIALOG_ID_NO_ACCESSIBILITY_SERVICES = 1; 134 135 // Auxiliary members. 136 private final static SimpleStringSplitter sStringColonSplitter = 137 new SimpleStringSplitter(ENABLED_ACCESSIBILITY_SERVICES_SEPARATOR); 138 139 private static final Set<ComponentName> sInstalledServices = new HashSet<ComponentName>(); 140 141 private final Map<String, String> mLongPressTimeoutValuetoTitleMap = 142 new HashMap<String, String>(); 143 144 private final Configuration mCurConfig = new Configuration(); 145 146 private final PackageMonitor mSettingsPackageMonitor = new SettingsPackageMonitor(); 147 148 private final Handler mHandler = new Handler() { 149 @Override 150 public void dispatchMessage(Message msg) { 151 super.dispatchMessage(msg); 152 loadInstalledServices(); 153 updateServicesPreferences(); 154 } 155 }; 156 157 // Preference controls. 158 private PreferenceCategory mServicesCategory; 159 private PreferenceCategory mSystemsCategory; 160 161 private CheckBoxPreference mToggleLargeTextPreference; 162 private CheckBoxPreference mTogglePowerButtonEndsCallPreference; 163 private CheckBoxPreference mToggleAutoRotateScreenPreference; 164 private CheckBoxPreference mToggleSpeakPasswordPreference; 165 private Preference mToggleTouchExplorationPreference; 166 private ListPreference mSelectLongPressTimeoutPreference; 167 private AccessibilityEnableScriptInjectionPreference mToggleScriptInjectionPreference; 168 private Preference mNoServicesMessagePreference; 169 170 private int mLongPressTimeoutDefault; 171 172 @Override 173 public void onCreate(Bundle icicle) { 174 super.onCreate(icicle); 175 addPreferencesFromResource(R.xml.accessibility_settings); 176 initializeAllPreferences(); 177 } 178 179 @Override 180 public void onResume() { 181 super.onResume(); 182 loadInstalledServices(); 183 updateAllPreferences(); 184 if (mServicesCategory.getPreference(0) == mNoServicesMessagePreference) { 185 offerInstallAccessibilitySerivceOnce(); 186 } 187 mSettingsPackageMonitor.register(getActivity(), false); 188 } 189 190 @Override 191 public void onPause() { 192 mSettingsPackageMonitor.unregister(); 193 super.onPause(); 194 } 195 196 public boolean onPreferenceChange(Preference preference, Object newValue) { 197 if (preference == mSelectLongPressTimeoutPreference) { 198 String stringValue = (String) newValue; 199 Settings.Secure.putInt(getContentResolver(), 200 Settings.Secure.LONG_PRESS_TIMEOUT, Integer.parseInt(stringValue)); 201 mSelectLongPressTimeoutPreference.setSummary( 202 mLongPressTimeoutValuetoTitleMap.get(stringValue)); 203 return true; 204 } 205 return false; 206 } 207 208 @Override 209 public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) { 210 if (mToggleLargeTextPreference == preference) { 211 handleToggleLargeTextPreferenceClick(); 212 return true; 213 } else if (mTogglePowerButtonEndsCallPreference == preference) { 214 handleTogglePowerButtonEndsCallPreferenceClick(); 215 return true; 216 } else if (mToggleAutoRotateScreenPreference == preference) { 217 handleToggleAutoRotateScreenPreferenceClick(); 218 return true; 219 } else if (mToggleSpeakPasswordPreference == preference) { 220 handleToggleSpeakPasswordPreferenceClick(); 221 } 222 return super.onPreferenceTreeClick(preferenceScreen, preference); 223 } 224 225 private void handleToggleLargeTextPreferenceClick() { 226 try { 227 mCurConfig.fontScale = mToggleLargeTextPreference.isChecked() ? LARGE_FONT_SCALE : 1; 228 ActivityManagerNative.getDefault().updatePersistentConfiguration(mCurConfig); 229 } catch (RemoteException re) { 230 /* ignore */ 231 } 232 } 233 234 private void handleTogglePowerButtonEndsCallPreferenceClick() { 235 Settings.Secure.putInt(getContentResolver(), 236 Settings.Secure.INCALL_POWER_BUTTON_BEHAVIOR, 237 (mTogglePowerButtonEndsCallPreference.isChecked() 238 ? Settings.Secure.INCALL_POWER_BUTTON_BEHAVIOR_HANGUP 239 : Settings.Secure.INCALL_POWER_BUTTON_BEHAVIOR_SCREEN_OFF)); 240 } 241 242 private void handleToggleAutoRotateScreenPreferenceClick() { 243 try { 244 IWindowManager wm = IWindowManager.Stub.asInterface( 245 ServiceManager.getService(Context.WINDOW_SERVICE)); 246 if (mToggleAutoRotateScreenPreference.isChecked()) { 247 wm.thawRotation(); 248 } else { 249 wm.freezeRotation(Surface.ROTATION_0); 250 } 251 } catch (RemoteException exc) { 252 Log.w(TAG, "Unable to save auto-rotate setting"); 253 } 254 } 255 256 private void handleToggleSpeakPasswordPreferenceClick() { 257 Settings.Secure.putInt(getContentResolver(), 258 Settings.Secure.ACCESSIBILITY_SPEAK_PASSWORD, 259 mToggleSpeakPasswordPreference.isChecked() ? 1 : 0); 260 } 261 262 private void initializeAllPreferences() { 263 mServicesCategory = (PreferenceCategory) findPreference(SERVICES_CATEGORY); 264 mSystemsCategory = (PreferenceCategory) findPreference(SYSTEM_CATEGORY); 265 266 // Large text. 267 mToggleLargeTextPreference = 268 (CheckBoxPreference) findPreference(TOGGLE_LARGE_TEXT_PREFERENCE); 269 270 // Power button ends calls. 271 mTogglePowerButtonEndsCallPreference = 272 (CheckBoxPreference) findPreference(TOGGLE_POWER_BUTTON_ENDS_CALL_PREFERENCE); 273 if (!KeyCharacterMap.deviceHasKey(KeyEvent.KEYCODE_POWER) 274 || !Utils.isVoiceCapable(getActivity())) { 275 mSystemsCategory.removePreference(mTogglePowerButtonEndsCallPreference); 276 } 277 278 // Auto-rotate screen 279 mToggleAutoRotateScreenPreference = 280 (CheckBoxPreference) findPreference(TOGGLE_AUTO_ROTATE_SCREEN_PREFERENCE); 281 282 // Speak passwords. 283 mToggleSpeakPasswordPreference = 284 (CheckBoxPreference) findPreference(TOGGLE_SPEAK_PASSWORD_PREFERENCE); 285 286 // Touch exploration enabled. 287 mToggleTouchExplorationPreference = findPreference(TOGGLE_TOUCH_EXPLORATION_PREFERENCE); 288 289 // Long press timeout. 290 mSelectLongPressTimeoutPreference = 291 (ListPreference) findPreference(SELECT_LONG_PRESS_TIMEOUT_PREFERENCE); 292 mSelectLongPressTimeoutPreference.setOnPreferenceChangeListener(this); 293 if (mLongPressTimeoutValuetoTitleMap.size() == 0) { 294 String[] timeoutValues = getResources().getStringArray( 295 R.array.long_press_timeout_selector_values); 296 mLongPressTimeoutDefault = Integer.parseInt(timeoutValues[0]); 297 String[] timeoutTitles = getResources().getStringArray( 298 R.array.long_press_timeout_selector_titles); 299 final int timeoutValueCount = timeoutValues.length; 300 for (int i = 0; i < timeoutValueCount; i++) { 301 mLongPressTimeoutValuetoTitleMap.put(timeoutValues[i], timeoutTitles[i]); 302 } 303 } 304 305 // Script injection. 306 mToggleScriptInjectionPreference = (AccessibilityEnableScriptInjectionPreference) 307 findPreference(TOGGLE_SCRIPT_INJECTION_PREFERENCE); 308 } 309 310 private void updateAllPreferences() { 311 updateServicesPreferences(); 312 updateSystemPreferences(); 313 } 314 315 private void updateServicesPreferences() { 316 // Since services category is auto generated we have to do a pass 317 // to generate it since services can come and go and then based on 318 // the global accessibility state to decided whether it is enabled. 319 320 // Generate. 321 mServicesCategory.removeAll(); 322 323 AccessibilityManager accessibilityManager = AccessibilityManager.getInstance(getActivity()); 324 325 List<AccessibilityServiceInfo> installedServices = 326 accessibilityManager.getInstalledAccessibilityServiceList(); 327 Set<ComponentName> enabledServices = getEnabledServicesFromSettings(getActivity()); 328 329 final boolean accessibilityEnabled = Settings.Secure.getInt(getContentResolver(), 330 Settings.Secure.ACCESSIBILITY_ENABLED, 0) == 1; 331 332 for (int i = 0, count = installedServices.size(); i < count; ++i) { 333 AccessibilityServiceInfo info = installedServices.get(i); 334 335 PreferenceScreen preference = getPreferenceManager().createPreferenceScreen( 336 getActivity()); 337 String title = info.getResolveInfo().loadLabel(getPackageManager()).toString(); 338 339 ServiceInfo serviceInfo = info.getResolveInfo().serviceInfo; 340 ComponentName componentName = new ComponentName(serviceInfo.packageName, 341 serviceInfo.name); 342 343 preference.setKey(componentName.flattenToString()); 344 345 preference.setTitle(title); 346 final boolean serviceEnabled = accessibilityEnabled 347 && enabledServices.contains(componentName); 348 if (serviceEnabled) { 349 preference.setSummary(getString(R.string.accessibility_service_state_on)); 350 } else { 351 preference.setSummary(getString(R.string.accessibility_service_state_off)); 352 } 353 354 preference.setOrder(i); 355 preference.setFragment(ToggleAccessibilityServiceFragment.class.getName()); 356 preference.setPersistent(true); 357 358 Bundle extras = preference.getExtras(); 359 extras.putString(EXTRA_PREFERENCE_KEY, preference.getKey()); 360 extras.putBoolean(EXTRA_CHECKED, serviceEnabled); 361 extras.putString(EXTRA_TITLE, title); 362 363 String description = info.getDescription(); 364 if (TextUtils.isEmpty(description)) { 365 description = getString(R.string.accessibility_service_default_description); 366 } 367 extras.putString(EXTRA_SUMMARY, description); 368 369 CharSequence applicationLabel = info.getResolveInfo().loadLabel(getPackageManager()); 370 371 extras.putString(EXTRA_ENABLE_WARNING_TITLE, getString( 372 R.string.accessibility_service_security_warning_title, applicationLabel)); 373 extras.putString(EXTRA_ENABLE_WARNING_MESSAGE, getString( 374 R.string.accessibility_service_security_warning_summary, applicationLabel)); 375 376 extras.putString(EXTRA_DISABLE_WARNING_TITLE, getString( 377 R.string.accessibility_service_disable_warning_title, 378 applicationLabel)); 379 extras.putString(EXTRA_DISABLE_WARNING_MESSAGE, getString( 380 R.string.accessibility_service_disable_warning_summary, 381 applicationLabel)); 382 383 String settingsClassName = info.getSettingsActivityName(); 384 if (!TextUtils.isEmpty(settingsClassName)) { 385 extras.putString(EXTRA_SETTINGS_TITLE, 386 getString(R.string.accessibility_menu_item_settings)); 387 extras.putString(EXTRA_SETTINGS_COMPONENT_NAME, 388 new ComponentName(info.getResolveInfo().serviceInfo.packageName, 389 settingsClassName).flattenToString()); 390 } 391 392 mServicesCategory.addPreference(preference); 393 } 394 395 if (mServicesCategory.getPreferenceCount() == 0) { 396 if (mNoServicesMessagePreference == null) { 397 mNoServicesMessagePreference = new Preference(getActivity()) { 398 @Override 399 protected void onBindView(View view) { 400 super.onBindView(view); 401 402 LinearLayout containerView = 403 (LinearLayout) view.findViewById(R.id.message_container); 404 containerView.setGravity(Gravity.CENTER); 405 406 TextView summaryView = (TextView) view.findViewById(R.id.summary); 407 String title = getString(R.string.accessibility_no_services_installed); 408 summaryView.setText(title); 409 } 410 }; 411 mNoServicesMessagePreference.setPersistent(false); 412 mNoServicesMessagePreference.setLayoutResource( 413 R.layout.text_description_preference); 414 mNoServicesMessagePreference.setSelectable(false); 415 } 416 mServicesCategory.addPreference(mNoServicesMessagePreference); 417 } 418 } 419 420 private void updateSystemPreferences() { 421 // Large text. 422 try { 423 mCurConfig.updateFrom(ActivityManagerNative.getDefault().getConfiguration()); 424 } catch (RemoteException re) { 425 /* ignore */ 426 } 427 mToggleLargeTextPreference.setChecked(mCurConfig.fontScale == LARGE_FONT_SCALE); 428 429 // Power button ends calls. 430 if (KeyCharacterMap.deviceHasKey(KeyEvent.KEYCODE_POWER) 431 && Utils.isVoiceCapable(getActivity())) { 432 final int incallPowerBehavior = Settings.Secure.getInt(getContentResolver(), 433 Settings.Secure.INCALL_POWER_BUTTON_BEHAVIOR, 434 Settings.Secure.INCALL_POWER_BUTTON_BEHAVIOR_DEFAULT); 435 final boolean powerButtonEndsCall = 436 (incallPowerBehavior == Settings.Secure.INCALL_POWER_BUTTON_BEHAVIOR_HANGUP); 437 mTogglePowerButtonEndsCallPreference.setChecked(powerButtonEndsCall); 438 } 439 440 // Auto-rotate screen 441 final boolean autoRotationEnabled = Settings.System.getInt(getContentResolver(), 442 Settings.System.ACCELEROMETER_ROTATION, 0) != 0; 443 mToggleAutoRotateScreenPreference.setChecked(autoRotationEnabled); 444 445 // Speak passwords. 446 final boolean speakPasswordEnabled = Settings.Secure.getInt(getContentResolver(), 447 Settings.Secure.ACCESSIBILITY_SPEAK_PASSWORD, 0) != 0; 448 mToggleSpeakPasswordPreference.setChecked(speakPasswordEnabled); 449 450 // Touch exploration enabled. 451 if (AccessibilityManager.getInstance(getActivity()).isEnabled()) { 452 mSystemsCategory.addPreference(mToggleTouchExplorationPreference); 453 final boolean touchExplorationEnabled = (Settings.Secure.getInt(getContentResolver(), 454 Settings.Secure.TOUCH_EXPLORATION_ENABLED, 0) == 1); 455 if (touchExplorationEnabled) { 456 mToggleTouchExplorationPreference.setSummary( 457 getString(R.string.accessibility_service_state_on)); 458 mToggleTouchExplorationPreference.getExtras().putBoolean(EXTRA_CHECKED, true); 459 } else { 460 mToggleTouchExplorationPreference.setSummary( 461 getString(R.string.accessibility_service_state_off)); 462 mToggleTouchExplorationPreference.getExtras().putBoolean(EXTRA_CHECKED, false); 463 } 464 465 } else { 466 mSystemsCategory.removePreference(mToggleTouchExplorationPreference); 467 } 468 469 // Long press timeout. 470 final int longPressTimeout = Settings.Secure.getInt(getContentResolver(), 471 Settings.Secure.LONG_PRESS_TIMEOUT, mLongPressTimeoutDefault); 472 String value = String.valueOf(longPressTimeout); 473 mSelectLongPressTimeoutPreference.setValue(value); 474 mSelectLongPressTimeoutPreference.setSummary(mLongPressTimeoutValuetoTitleMap.get(value)); 475 476 // Script injection. 477 final boolean scriptInjectionAllowed = (Settings.Secure.getInt(getContentResolver(), 478 Settings.Secure.ACCESSIBILITY_SCRIPT_INJECTION, 0) == 1); 479 mToggleScriptInjectionPreference.setInjectionAllowed(scriptInjectionAllowed); 480 } 481 482 private void offerInstallAccessibilitySerivceOnce() { 483 // There is always one preference - if no services it is just a message. 484 if (mServicesCategory.getPreference(0) != mNoServicesMessagePreference) { 485 return; 486 } 487 SharedPreferences preferences = getActivity().getPreferences(Context.MODE_PRIVATE); 488 final boolean offerInstallService = !preferences.getBoolean( 489 KEY_INSTALL_ACCESSIBILITY_SERVICE_OFFERED_ONCE, false); 490 if (offerInstallService) { 491 preferences.edit().putBoolean(KEY_INSTALL_ACCESSIBILITY_SERVICE_OFFERED_ONCE, 492 true).commit(); 493 // Notify user that they do not have any accessibility 494 // services installed and direct them to Market to get TalkBack. 495 showDialog(DIALOG_ID_NO_ACCESSIBILITY_SERVICES); 496 } 497 } 498 499 @Override 500 public Dialog onCreateDialog(int dialogId) { 501 switch (dialogId) { 502 case DIALOG_ID_NO_ACCESSIBILITY_SERVICES: 503 return new AlertDialog.Builder(getActivity()) 504 .setTitle(R.string.accessibility_service_no_apps_title) 505 .setMessage(R.string.accessibility_service_no_apps_message) 506 .setPositiveButton(android.R.string.ok, 507 new DialogInterface.OnClickListener() { 508 public void onClick(DialogInterface dialog, int which) { 509 // dismiss the dialog before launching the activity otherwise 510 // the dialog removal occurs after onSaveInstanceState which 511 // triggers an exception 512 removeDialog(DIALOG_ID_NO_ACCESSIBILITY_SERVICES); 513 String screenreaderMarketLink = SystemProperties.get( 514 SYSTEM_PROPERTY_MARKET_URL, 515 DEFAULT_SCREENREADER_MARKET_LINK); 516 Uri marketUri = Uri.parse(screenreaderMarketLink); 517 Intent marketIntent = new Intent(Intent.ACTION_VIEW, marketUri); 518 startActivity(marketIntent); 519 } 520 }) 521 .setNegativeButton(android.R.string.cancel, null) 522 .create(); 523 default: 524 return null; 525 } 526 } 527 528 private void loadInstalledServices() { 529 List<AccessibilityServiceInfo> installedServiceInfos = 530 AccessibilityManager.getInstance(getActivity()) 531 .getInstalledAccessibilityServiceList(); 532 Set<ComponentName> installedServices = sInstalledServices; 533 installedServices.clear(); 534 final int installedServiceInfoCount = installedServiceInfos.size(); 535 for (int i = 0; i < installedServiceInfoCount; i++) { 536 ResolveInfo resolveInfo = installedServiceInfos.get(i).getResolveInfo(); 537 ComponentName installedService = new ComponentName( 538 resolveInfo.serviceInfo.packageName, 539 resolveInfo.serviceInfo.name); 540 installedServices.add(installedService); 541 } 542 } 543 544 private static Set<ComponentName> getEnabledServicesFromSettings(Context context) { 545 String enabledServicesSetting = Settings.Secure.getString(context.getContentResolver(), 546 Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES); 547 if (enabledServicesSetting == null) { 548 enabledServicesSetting = ""; 549 } 550 Set<ComponentName> enabledServices = new HashSet<ComponentName>(); 551 SimpleStringSplitter colonSplitter = sStringColonSplitter; 552 colonSplitter.setString(enabledServicesSetting); 553 while (colonSplitter.hasNext()) { 554 String componentNameString = colonSplitter.next(); 555 ComponentName enabledService = ComponentName.unflattenFromString( 556 componentNameString); 557 if (enabledService != null) { 558 enabledServices.add(enabledService); 559 } 560 } 561 return enabledServices; 562 } 563 564 private class SettingsPackageMonitor extends PackageMonitor { 565 566 @Override 567 public void onPackageAdded(String packageName, int uid) { 568 Message message = mHandler.obtainMessage(); 569 mHandler.sendMessageDelayed(message, DELAY_UPDATE_SERVICES_MILLIS); 570 } 571 572 @Override 573 public void onPackageAppeared(String packageName, int reason) { 574 Message message = mHandler.obtainMessage(); 575 mHandler.sendMessageDelayed(message, DELAY_UPDATE_SERVICES_MILLIS); 576 } 577 578 @Override 579 public void onPackageDisappeared(String packageName, int reason) { 580 Message message = mHandler.obtainMessage(); 581 mHandler.sendMessageDelayed(message, DELAY_UPDATE_SERVICES_MILLIS); 582 } 583 584 @Override 585 public void onPackageRemoved(String packageName, int uid) { 586 Message message = mHandler.obtainMessage(); 587 mHandler.sendMessageDelayed(message, DELAY_UPDATE_SERVICES_MILLIS); 588 } 589 } 590 591 private static ToggleSwitch createAndAddActionBarToggleSwitch(Activity activity) { 592 ToggleSwitch toggleSwitch = new ToggleSwitch(activity); 593 final int padding = activity.getResources().getDimensionPixelSize( 594 R.dimen.action_bar_switch_padding); 595 toggleSwitch.setPadding(0, 0, padding, 0); 596 activity.getActionBar().setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM, 597 ActionBar.DISPLAY_SHOW_CUSTOM); 598 activity.getActionBar().setCustomView(toggleSwitch, 599 new ActionBar.LayoutParams(ActionBar.LayoutParams.WRAP_CONTENT, 600 ActionBar.LayoutParams.WRAP_CONTENT, 601 Gravity.CENTER_VERTICAL | Gravity.RIGHT)); 602 return toggleSwitch; 603 } 604 605 public static class ToggleSwitch extends Switch { 606 607 private OnBeforeCheckedChangeListener mOnBeforeListener; 608 609 public static interface OnBeforeCheckedChangeListener { 610 public boolean onBeforeCheckedChanged(ToggleSwitch toggleSwitch, boolean checked); 611 } 612 613 public ToggleSwitch(Context context) { 614 super(context); 615 } 616 617 public void setOnBeforeCheckedChangeListener(OnBeforeCheckedChangeListener listener) { 618 mOnBeforeListener = listener; 619 } 620 621 @Override 622 public void setChecked(boolean checked) { 623 if (mOnBeforeListener != null 624 && mOnBeforeListener.onBeforeCheckedChanged(this, checked)) { 625 return; 626 } 627 super.setChecked(checked); 628 } 629 630 public void setCheckedInternal(boolean checked) { 631 super.setChecked(checked); 632 } 633 } 634 635 public static class ToggleAccessibilityServiceFragment extends TogglePreferenceFragment { 636 @Override 637 public void onPreferenceToggled(String preferenceKey, boolean enabled) { 638 // Parse the enabled services. 639 Set<ComponentName> enabledServices = getEnabledServicesFromSettings(getActivity()); 640 641 // Determine enabled services and accessibility state. 642 ComponentName toggledService = ComponentName.unflattenFromString(preferenceKey); 643 final boolean accessibilityEnabled; 644 if (enabled) { 645 // Enabling at least one service enables accessibility. 646 accessibilityEnabled = true; 647 enabledServices.add(toggledService); 648 } else { 649 // Check how many enabled and installed services are present. 650 int enabledAndInstalledServiceCount = 0; 651 Set<ComponentName> installedServices = sInstalledServices; 652 for (ComponentName enabledService : enabledServices) { 653 if (installedServices.contains(enabledService)) { 654 enabledAndInstalledServiceCount++; 655 } 656 } 657 // Disabling the last service disables accessibility. 658 accessibilityEnabled = enabledAndInstalledServiceCount > 1 659 || (enabledAndInstalledServiceCount == 1 660 && !installedServices.contains(toggledService)); 661 enabledServices.remove(toggledService); 662 } 663 664 // Update the enabled services setting. 665 StringBuilder enabledServicesBuilder = new StringBuilder(); 666 // Keep the enabled services even if they are not installed since we have 667 // no way to know whether the application restore process has completed. 668 // In general the system should be responsible for the clean up not settings. 669 for (ComponentName enabledService : enabledServices) { 670 enabledServicesBuilder.append(enabledService.flattenToString()); 671 enabledServicesBuilder.append(ENABLED_ACCESSIBILITY_SERVICES_SEPARATOR); 672 } 673 final int enabledServicesBuilderLength = enabledServicesBuilder.length(); 674 if (enabledServicesBuilderLength > 0) { 675 enabledServicesBuilder.deleteCharAt(enabledServicesBuilderLength - 1); 676 } 677 Settings.Secure.putString(getContentResolver(), 678 Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, 679 enabledServicesBuilder.toString()); 680 681 // Update accessibility enabled. 682 Settings.Secure.putInt(getContentResolver(), 683 Settings.Secure.ACCESSIBILITY_ENABLED, accessibilityEnabled ? 1 : 0); 684 } 685 } 686 687 public static class ToggleTouchExplorationFragment extends TogglePreferenceFragment { 688 @Override 689 public void onPreferenceToggled(String preferenceKey, boolean enabled) { 690 Settings.Secure.putInt(getContentResolver(), 691 Settings.Secure.TOUCH_EXPLORATION_ENABLED, enabled ? 1 : 0); 692 if (enabled) { 693 SharedPreferences preferences = getActivity().getPreferences(Context.MODE_PRIVATE); 694 final boolean launchAccessibilityTutorial = !preferences.getBoolean( 695 KEY_ACCESSIBILITY_TUTORIAL_LAUNCHED_ONCE, false); 696 if (launchAccessibilityTutorial) { 697 preferences.edit().putBoolean(KEY_ACCESSIBILITY_TUTORIAL_LAUNCHED_ONCE, 698 true).commit(); 699 Intent intent = new Intent(AccessibilityTutorialActivity.ACTION); 700 getActivity().startActivity(intent); 701 } 702 } 703 } 704 } 705 706 private abstract static class TogglePreferenceFragment extends SettingsPreferenceFragment 707 implements DialogInterface.OnClickListener { 708 709 private static final int DIALOG_ID_ENABLE_WARNING = 1; 710 private static final int DIALOG_ID_DISABLE_WARNING = 2; 711 712 private String mPreferenceKey; 713 714 private ToggleSwitch mToggleSwitch; 715 716 private CharSequence mEnableWarningTitle; 717 private CharSequence mEnableWarningMessage; 718 private CharSequence mDisableWarningTitle; 719 private CharSequence mDisableWarningMessage; 720 private Preference mSummaryPreference; 721 722 private CharSequence mSettingsTitle; 723 private Intent mSettingsIntent; 724 725 private int mShownDialogId; 726 727 // TODO: Showing sub-sub fragment does not handle the activity title 728 // so we do it but this is wrong. Do a real fix when there is time. 729 private CharSequence mOldActivityTitle; 730 731 @Override 732 public void onCreate(Bundle savedInstanceState) { 733 super.onCreate(savedInstanceState); 734 PreferenceScreen preferenceScreen = getPreferenceManager().createPreferenceScreen( 735 getActivity()); 736 setPreferenceScreen(preferenceScreen); 737 mSummaryPreference = new Preference(getActivity()) { 738 @Override 739 protected void onBindView(View view) { 740 super.onBindView(view); 741 TextView summaryView = (TextView) view.findViewById(R.id.summary); 742 summaryView.setText(getSummary()); 743 sendAccessibilityEvent(summaryView); 744 } 745 746 private void sendAccessibilityEvent(View view) { 747 // Since the view is still not attached we create, populate, 748 // and send the event directly since we do not know when it 749 // will be attached and posting commands is not as clean. 750 AccessibilityManager accessibilityManager = 751 AccessibilityManager.getInstance(getActivity()); 752 if (accessibilityManager.isEnabled()) { 753 AccessibilityEvent event = AccessibilityEvent.obtain(); 754 event.setEventType(AccessibilityEvent.TYPE_VIEW_FOCUSED); 755 view.onInitializeAccessibilityEvent(event); 756 view.dispatchPopulateAccessibilityEvent(event); 757 accessibilityManager.sendAccessibilityEvent(event); 758 } 759 } 760 }; 761 mSummaryPreference.setPersistent(false); 762 mSummaryPreference.setLayoutResource(R.layout.text_description_preference); 763 preferenceScreen.addPreference(mSummaryPreference); 764 } 765 766 @Override 767 public void onViewCreated(View view, Bundle savedInstanceState) { 768 super.onViewCreated(view, savedInstanceState); 769 installActionBarToggleSwitch(); 770 processArguments(); 771 getListView().setDivider(null); 772 getListView().setEnabled(false); 773 } 774 775 @Override 776 public void onDestroyView() { 777 getActivity().getActionBar().setCustomView(null); 778 if (mOldActivityTitle != null) { 779 getActivity().getActionBar().setTitle(mOldActivityTitle); 780 } 781 mToggleSwitch.setOnBeforeCheckedChangeListener(null); 782 super.onDestroyView(); 783 } 784 785 public abstract void onPreferenceToggled(String preferenceKey, boolean value); 786 787 @Override 788 public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { 789 super.onCreateOptionsMenu(menu, inflater); 790 MenuItem menuItem = menu.add(mSettingsTitle); 791 menuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM); 792 menuItem.setIntent(mSettingsIntent); 793 } 794 795 @Override 796 public Dialog onCreateDialog(int dialogId) { 797 CharSequence title = null; 798 CharSequence message = null; 799 switch (dialogId) { 800 case DIALOG_ID_ENABLE_WARNING: 801 mShownDialogId = DIALOG_ID_ENABLE_WARNING; 802 title = mEnableWarningTitle; 803 message = mEnableWarningMessage; 804 break; 805 case DIALOG_ID_DISABLE_WARNING: 806 mShownDialogId = DIALOG_ID_DISABLE_WARNING; 807 title = mDisableWarningTitle; 808 message = mDisableWarningMessage; 809 break; 810 default: 811 throw new IllegalArgumentException(); 812 } 813 return new AlertDialog.Builder(getActivity()) 814 .setTitle(title) 815 .setIcon(android.R.drawable.ic_dialog_alert) 816 .setMessage(message) 817 .setCancelable(true) 818 .setPositiveButton(android.R.string.ok, this) 819 .setNegativeButton(android.R.string.cancel, this) 820 .create(); 821 } 822 823 @Override 824 public void onClick(DialogInterface dialog, int which) { 825 final boolean checked; 826 switch (which) { 827 case DialogInterface.BUTTON_POSITIVE: 828 checked = (mShownDialogId == DIALOG_ID_ENABLE_WARNING); 829 mToggleSwitch.setCheckedInternal(checked); 830 getArguments().putBoolean(EXTRA_CHECKED, checked); 831 onPreferenceToggled(mPreferenceKey, checked); 832 break; 833 case DialogInterface.BUTTON_NEGATIVE: 834 checked = (mShownDialogId == DIALOG_ID_DISABLE_WARNING); 835 mToggleSwitch.setCheckedInternal(checked); 836 getArguments().putBoolean(EXTRA_CHECKED, checked); 837 onPreferenceToggled(mPreferenceKey, checked); 838 break; 839 default: 840 throw new IllegalArgumentException(); 841 } 842 } 843 844 private void installActionBarToggleSwitch() { 845 mToggleSwitch = createAndAddActionBarToggleSwitch(getActivity()); 846 mToggleSwitch.setOnBeforeCheckedChangeListener(new OnBeforeCheckedChangeListener() { 847 @Override 848 public boolean onBeforeCheckedChanged(ToggleSwitch toggleSwitch, boolean checked) { 849 if (checked) { 850 if (!TextUtils.isEmpty(mEnableWarningMessage)) { 851 toggleSwitch.setCheckedInternal(false); 852 getArguments().putBoolean(EXTRA_CHECKED, false); 853 showDialog(DIALOG_ID_ENABLE_WARNING); 854 return true; 855 } 856 onPreferenceToggled(mPreferenceKey, true); 857 } else { 858 if (!TextUtils.isEmpty(mDisableWarningMessage)) { 859 toggleSwitch.setCheckedInternal(true); 860 getArguments().putBoolean(EXTRA_CHECKED, true); 861 showDialog(DIALOG_ID_DISABLE_WARNING); 862 return true; 863 } 864 onPreferenceToggled(mPreferenceKey, false); 865 } 866 return false; 867 } 868 }); 869 } 870 871 private void processArguments() { 872 Bundle arguments = getArguments(); 873 874 // Key. 875 mPreferenceKey = arguments.getString(EXTRA_PREFERENCE_KEY); 876 877 // Enabled. 878 final boolean enabled = arguments.getBoolean(EXTRA_CHECKED); 879 mToggleSwitch.setCheckedInternal(enabled); 880 881 // Title. 882 PreferenceActivity activity = (PreferenceActivity) getActivity(); 883 if (!activity.onIsMultiPane() || activity.onIsHidingHeaders()) { 884 mOldActivityTitle = getActivity().getTitle(); 885 String title = arguments.getString(EXTRA_TITLE); 886 getActivity().getActionBar().setTitle(title); 887 } 888 889 // Summary. 890 String summary = arguments.getString(EXTRA_SUMMARY); 891 mSummaryPreference.setSummary(summary); 892 893 // Settings title and intent. 894 String settingsTitle = arguments.getString(EXTRA_SETTINGS_TITLE); 895 String settingsComponentName = arguments.getString(EXTRA_SETTINGS_COMPONENT_NAME); 896 if (!TextUtils.isEmpty(settingsTitle) && !TextUtils.isEmpty(settingsComponentName)) { 897 Intent settingsIntent = new Intent(Intent.ACTION_MAIN).setComponent( 898 ComponentName.unflattenFromString(settingsComponentName.toString())); 899 if (!getPackageManager().queryIntentActivities(settingsIntent, 0).isEmpty()) { 900 mSettingsTitle = settingsTitle; 901 mSettingsIntent = settingsIntent; 902 setHasOptionsMenu(true); 903 } 904 } 905 906 // Enable warning title. 907 mEnableWarningTitle = arguments.getCharSequence( 908 AccessibilitySettings.EXTRA_ENABLE_WARNING_TITLE); 909 910 // Enable warning message. 911 mEnableWarningMessage = arguments.getCharSequence( 912 AccessibilitySettings.EXTRA_ENABLE_WARNING_MESSAGE); 913 914 // Disable warning title. 915 mDisableWarningTitle = arguments.getString( 916 AccessibilitySettings.EXTRA_DISABLE_WARNING_TITLE); 917 918 // Disable warning message. 919 mDisableWarningMessage = arguments.getString( 920 AccessibilitySettings.EXTRA_DISABLE_WARNING_MESSAGE); 921 } 922 } 923} 924