diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 6e3bfde7f57..76bb7020db5 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -249,8 +249,15 @@
-
-
+
+
+
+
+
+
+
+
diff --git a/res/values/strings.xml b/res/values/strings.xml
index f4a68464eeb..5b04e51f641 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -1572,6 +1572,33 @@ found in the list of installed applications.
Count
Usage time
+
+
+
+
+ Accessibility
+
+ Manage accessibility options
+
+ Accessibility
+
+ Enable accessibility
+
+ Disable accessibility
+
+ Accessibility services
+
+ No installed accessibility services.
+
+ This accessibility service may be able to collect
+ all the text you type, including personal data credit card numbers except passwords.
+ It may also log your user interface interactions. It comes from the application
+ %1$s. Enable this accessibility service?
+
+ Disable accessibility?
+
diff --git a/res/xml/accessibility_settings.xml b/res/xml/accessibility_settings.xml
new file mode 100644
index 00000000000..d419540a279
--- /dev/null
+++ b/res/xml/accessibility_settings.xml
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/res/xml/settings.xml b/res/xml/settings.xml
index 56feed8b3b4..10712ebc7d1 100644
--- a/res/xml/settings.xml
+++ b/res/xml/settings.xml
@@ -115,7 +115,6 @@
android:targetClass="com.android.settings.LanguageSettings" />
-
+
+
+
+
+
mAccessibilityServices =
+ new LinkedHashMap();
+
+ private TextUtils.SimpleStringSplitter mStringColonSplitter =
+ new TextUtils.SimpleStringSplitter(':');
+
+ private PreferenceGroup mAccessibilityServicesCategory;
+
+ @Override
+ protected void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+ addPreferencesFromResource(R.xml.accessibility_settings);
+
+ mToggleCheckBox = (CheckBoxPreference) findPreference(
+ TOGGLE_ACCESSIBILITY_SERVICE_CHECKBOX);
+
+ addAccessibilitServicePreferences();
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+
+ final HashSet enabled = new HashSet();
+ String settingValue = Settings.Secure.getString(getContentResolver(),
+ Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES);
+ if (settingValue != null) {
+ TextUtils.SimpleStringSplitter splitter = mStringColonSplitter;
+ splitter.setString(settingValue);
+ while (splitter.hasNext()) {
+ enabled.add(splitter.next());
+ }
+ }
+
+ Map accessibilityServices = mAccessibilityServices;
+
+ for (String key : accessibilityServices.keySet()) {
+ CheckBoxPreference preference = (CheckBoxPreference) findPreference(key);
+ if (preference != null) {
+ preference.setChecked(enabled.contains(key));
+ }
+ }
+
+ int serviceState = Settings.Secure.getInt(getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_ENABLED, 0);
+
+ if (!accessibilityServices.isEmpty()) {
+ if (serviceState == 1) {
+ mToggleCheckBox.setChecked(true);
+ mToggleCheckBox.setSummaryOn(R.string.disable_accessibility_service_summary);
+ } else {
+ mToggleCheckBox.setSummaryOff(R.string.enable_accessibility_service_summary);
+ setAccessibilityServicePreferencesState(false);
+ }
+ mToggleCheckBox.setEnabled(true);
+ } else {
+ if (serviceState == 1) {
+ // no service and accessibility is enabled => disable
+ Settings.Secure.putInt(getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_ENABLED, 0);
+ setAccessibilityServicePreferencesState(false);
+ }
+ mToggleCheckBox.setSummaryOff(R.string.no_accessibility_services_summary);
+ mToggleCheckBox.setEnabled(false);
+ }
+ }
+
+ @Override
+ protected void onPause() {
+ super.onPause();
+
+ persistEnabledAccessibilityServices();
+ }
+
+ /**
+ * Sets the state of the preferences for enabling/disabling AccessibilityServices.
+ *
+ * @param isEnabled If to enable or disable the preferences.
+ */
+ private void setAccessibilityServicePreferencesState(boolean isEnabled) {
+ if (mAccessibilityServicesCategory == null) {
+ return;
+ }
+
+ int count = mAccessibilityServicesCategory.getPreferenceCount();
+ for (int i = 0; i < count; i++) {
+ mAccessibilityServicesCategory.getPreference(i).setEnabled(isEnabled);
+ }
+ }
+
+ @Override
+ public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) {
+ final String key = preference.getKey();
+
+ if (TOGGLE_ACCESSIBILITY_SERVICE_CHECKBOX.equals(key)) {
+ boolean isChecked = ((CheckBoxPreference) preference).isChecked();
+ handleEnableAccessibilityStateChange((CheckBoxPreference) preference);
+ } else if (preference instanceof CheckBoxPreference) {
+ handleEnableAccessibilityServiceStateChange((CheckBoxPreference) preference);
+ }
+
+ return super.onPreferenceTreeClick(preferenceScreen, preference);
+ }
+
+ /**
+ * Handles the change of the accessibility enabled setting state.
+ *
+ * @param preference The preference for enabling/disabling accessibility.
+ */
+ private void handleEnableAccessibilityStateChange(CheckBoxPreference preference) {
+ if (preference.isChecked()) {
+ Settings.Secure.putInt(getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_ENABLED, 1);
+ setAccessibilityServicePreferencesState(true);
+ } else {
+ final CheckBoxPreference checkBoxPreference = preference;
+ AlertDialog dialog = (new AlertDialog.Builder(this))
+ .setTitle(android.R.string.dialog_alert_title)
+ .setIcon(android.R.drawable.ic_dialog_alert)
+ .setMessage(getString(R.string.accessibility_service_disable_warning))
+ .setCancelable(true)
+ .setPositiveButton(android.R.string.ok,
+ new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int which) {
+ Settings.Secure.putInt(getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_ENABLED, 0);
+ setAccessibilityServicePreferencesState(false);
+ }
+ })
+ .setNegativeButton(android.R.string.cancel,
+ new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int which) {
+ checkBoxPreference.setChecked(true);
+ }
+ })
+ .create();
+ dialog.show();
+ }
+ }
+
+ /**
+ * Handles the change of the preference for enabling/disabling an AccessibilityService.
+ *
+ * @param preference The preference.
+ */
+ private void handleEnableAccessibilityServiceStateChange(CheckBoxPreference preference) {
+ if (preference.isChecked()) {
+ final CheckBoxPreference checkBoxPreference = preference;
+ AlertDialog dialog = (new AlertDialog.Builder(this))
+ .setTitle(android.R.string.dialog_alert_title)
+ .setIcon(android.R.drawable.ic_dialog_alert)
+ .setMessage(getString(R.string.accessibility_service_security_warning,
+ mAccessibilityServices.get(preference.getKey())
+ .applicationInfo.loadLabel(getPackageManager())))
+ .setCancelable(true)
+ .setPositiveButton(android.R.string.ok,
+ new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int which) {
+ checkBoxPreference.setChecked(true);
+ persistEnabledAccessibilityServices();
+ }
+ })
+ .setNegativeButton(android.R.string.cancel,
+ new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int which) {
+ checkBoxPreference.setChecked(false);
+ }
+ })
+ .create();
+ dialog.show();
+ } else {
+ persistEnabledAccessibilityServices();
+ }
+ }
+
+ /**
+ * Persists the Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES setting.
+ * The AccessibilityManagerService watches this property and manages the
+ * AccessibilityServices.
+ */
+ private void persistEnabledAccessibilityServices() {
+ StringBuilder builder = new StringBuilder(256);
+
+ int firstEnabled = -1;
+ for (String key : mAccessibilityServices.keySet()) {
+ CheckBoxPreference preference = (CheckBoxPreference) findPreference(key);
+ if (preference.isChecked()) {
+ builder.append(key);
+ builder.append(':');
+ }
+ }
+
+ Settings.Secure.putString(getContentResolver(),
+ Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, builder.toString());
+ }
+
+ /**
+ * Adds {@link CheckBoxPreference} for enabling or disabling an accessibility services.
+ */
+ private void addAccessibilitServicePreferences() {
+ AccessibilityManager accessibilityManager =
+ (AccessibilityManager) getSystemService(Service.ACCESSIBILITY_SERVICE);
+
+ List installedServices = accessibilityManager.getAccessibilityServiceList();
+
+ mAccessibilityServicesCategory =
+ (PreferenceGroup) findPreference(ACCESSIBILITY_SERVICES_CATEGORY);
+
+ if (installedServices.isEmpty()) {
+ getPreferenceScreen().removePreference(mAccessibilityServicesCategory);
+ mAccessibilityServicesCategory = null;
+ return;
+ }
+
+ for (int i = 0, count = installedServices.size(); i < count; ++i) {
+ ServiceInfo serviceInfo = installedServices.get(i);
+ String key = serviceInfo.packageName + "/" + serviceInfo.name;
+
+ mAccessibilityServices.put(key, serviceInfo);
+
+ CheckBoxPreference preference = new CheckBoxPreference(this);
+ preference.setKey(key);
+ preference.setTitle(serviceInfo.loadLabel(getPackageManager()));
+ mAccessibilityServicesCategory.addPreference(preference);
+ }
+ }
+}