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); + } + } +}