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