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