2 * Copyright (C) 2009 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package com.android.settings;
19 import android.app.AlertDialog;
20 import android.app.Service;
21 import android.content.DialogInterface;
22 import android.content.Intent;
23 import android.content.pm.ApplicationInfo;
24 import android.content.pm.PackageManager;
25 import android.content.pm.PackageManager.NameNotFoundException;
26 import android.content.pm.ServiceInfo;
27 import android.net.Uri;
28 import android.os.Bundle;
29 import android.os.SystemProperties;
30 import android.preference.CheckBoxPreference;
31 import android.preference.Preference;
32 import android.preference.PreferenceActivity;
33 import android.preference.PreferenceCategory;
34 import android.preference.PreferenceGroup;
35 import android.preference.PreferenceScreen;
36 import android.provider.Settings;
37 import android.text.TextUtils;
38 import android.view.KeyCharacterMap;
39 import android.view.KeyEvent;
40 import android.view.accessibility.AccessibilityManager;
42 import java.util.HashSet;
43 import java.util.LinkedHashMap;
44 import java.util.List;
48 * Activity with the accessibility settings.
50 public class AccessibilitySettings extends PreferenceActivity {
51 private static final String DEFAULT_SCREENREADER_MARKET_LINK =
52 "market://search?q=pname:com.google.android.marvin.talkback";
54 private final String TOGGLE_ACCESSIBILITY_SERVICE_CHECKBOX =
55 "toggle_accessibility_service_checkbox";
57 private static final String ACCESSIBILITY_SERVICES_CATEGORY =
58 "accessibility_services_category";
60 private static final String POWER_BUTTON_CATEGORY =
61 "power_button_category";
63 private final String POWER_BUTTON_ENDS_CALL_CHECKBOX =
64 "power_button_ends_call";
66 private CheckBoxPreference mToggleCheckBox;
68 private PreferenceCategory mPowerButtonCategory;
69 private CheckBoxPreference mPowerButtonEndsCallCheckBox;
71 private Map<String, ServiceInfo> mAccessibilityServices =
72 new LinkedHashMap<String, ServiceInfo>();
74 private TextUtils.SimpleStringSplitter mStringColonSplitter =
75 new TextUtils.SimpleStringSplitter(':');
77 private PreferenceGroup mAccessibilityServicesCategory;
80 protected void onCreate(Bundle icicle) {
81 super.onCreate(icicle);
82 addPreferencesFromResource(R.xml.accessibility_settings);
84 mToggleCheckBox = (CheckBoxPreference) findPreference(
85 TOGGLE_ACCESSIBILITY_SERVICE_CHECKBOX);
87 mPowerButtonCategory = (PreferenceCategory) findPreference(POWER_BUTTON_CATEGORY);
88 mPowerButtonEndsCallCheckBox = (CheckBoxPreference) findPreference(
89 POWER_BUTTON_ENDS_CALL_CHECKBOX);
91 addAccessibilitServicePreferences();
95 protected void onResume() {
98 final HashSet<String> enabled = new HashSet<String>();
99 String settingValue = Settings.Secure.getString(getContentResolver(),
100 Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES);
101 if (settingValue != null) {
102 TextUtils.SimpleStringSplitter splitter = mStringColonSplitter;
103 splitter.setString(settingValue);
104 while (splitter.hasNext()) {
105 enabled.add(splitter.next());
109 Map<String, ServiceInfo> accessibilityServices = mAccessibilityServices;
111 for (String key : accessibilityServices.keySet()) {
112 CheckBoxPreference preference = (CheckBoxPreference) findPreference(key);
113 if (preference != null) {
114 preference.setChecked(enabled.contains(key));
118 int serviceState = Settings.Secure.getInt(getContentResolver(),
119 Settings.Secure.ACCESSIBILITY_ENABLED, 0);
121 if (!accessibilityServices.isEmpty()) {
122 if (serviceState == 1) {
123 mToggleCheckBox.setChecked(true);
125 setAccessibilityServicePreferencesState(false);
127 mToggleCheckBox.setEnabled(true);
129 if (serviceState == 1) {
130 // no service and accessibility is enabled => disable
131 Settings.Secure.putInt(getContentResolver(),
132 Settings.Secure.ACCESSIBILITY_ENABLED, 0);
134 mToggleCheckBox.setEnabled(false);
135 // Notify user that they do not have any accessibility apps
136 // installed and direct them to Market to get TalkBack
137 displayNoAppsAlert();
140 if (KeyCharacterMap.deviceHasKey(KeyEvent.KEYCODE_POWER)) {
141 int incallPowerBehavior = Settings.Secure.getInt(getContentResolver(),
142 Settings.Secure.INCALL_POWER_BUTTON_BEHAVIOR,
143 Settings.Secure.INCALL_POWER_BUTTON_BEHAVIOR_DEFAULT);
144 // The checkbox is labeled "Power button ends call"; thus the in-call
145 // Power button behavior is INCALL_POWER_BUTTON_BEHAVIOR_HANGUP if
146 // checked, and INCALL_POWER_BUTTON_BEHAVIOR_SCREEN_OFF if unchecked.
147 boolean powerButtonCheckboxEnabled =
148 (incallPowerBehavior == Settings.Secure.INCALL_POWER_BUTTON_BEHAVIOR_HANGUP);
149 mPowerButtonEndsCallCheckBox.setChecked(powerButtonCheckboxEnabled);
150 mPowerButtonEndsCallCheckBox.setEnabled(true);
152 // No POWER key on the current device; this entire category is irrelevant.
153 getPreferenceScreen().removePreference(mPowerButtonCategory);
158 protected void onPause() {
161 persistEnabledAccessibilityServices();
165 * Sets the state of the preferences for enabling/disabling
166 * AccessibilityServices.
168 * @param isEnabled If to enable or disable the preferences.
170 private void setAccessibilityServicePreferencesState(boolean isEnabled) {
171 if (mAccessibilityServicesCategory == null) {
175 int count = mAccessibilityServicesCategory.getPreferenceCount();
176 for (int i = 0; i < count; i++) {
177 Preference pref = mAccessibilityServicesCategory.getPreference(i);
178 pref.setEnabled(isEnabled);
183 public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) {
184 final String key = preference.getKey();
186 if (TOGGLE_ACCESSIBILITY_SERVICE_CHECKBOX.equals(key)) {
187 boolean isChecked = ((CheckBoxPreference) preference).isChecked();
188 handleEnableAccessibilityStateChange((CheckBoxPreference) preference);
189 } else if (POWER_BUTTON_ENDS_CALL_CHECKBOX.equals(key)) {
190 boolean isChecked = ((CheckBoxPreference) preference).isChecked();
191 // The checkbox is labeled "Power button ends call"; thus the in-call
192 // Power button behavior is INCALL_POWER_BUTTON_BEHAVIOR_HANGUP if
193 // checked, and INCALL_POWER_BUTTON_BEHAVIOR_SCREEN_OFF if unchecked.
194 Settings.Secure.putInt(getContentResolver(),
195 Settings.Secure.INCALL_POWER_BUTTON_BEHAVIOR,
196 (isChecked ? Settings.Secure.INCALL_POWER_BUTTON_BEHAVIOR_HANGUP
197 : Settings.Secure.INCALL_POWER_BUTTON_BEHAVIOR_SCREEN_OFF));
198 } else if (preference instanceof CheckBoxPreference) {
199 handleEnableAccessibilityServiceStateChange((CheckBoxPreference) preference);
202 return super.onPreferenceTreeClick(preferenceScreen, preference);
206 * Handles the change of the accessibility enabled setting state.
208 * @param preference The preference for enabling/disabling accessibility.
210 private void handleEnableAccessibilityStateChange(CheckBoxPreference preference) {
211 if (preference.isChecked()) {
212 Settings.Secure.putInt(getContentResolver(),
213 Settings.Secure.ACCESSIBILITY_ENABLED, 1);
214 setAccessibilityServicePreferencesState(true);
216 final CheckBoxPreference checkBoxPreference = preference;
217 AlertDialog dialog = (new AlertDialog.Builder(this))
218 .setTitle(android.R.string.dialog_alert_title)
219 .setIcon(android.R.drawable.ic_dialog_alert)
220 .setMessage(getString(R.string.accessibility_service_disable_warning))
222 .setPositiveButton(android.R.string.ok,
223 new DialogInterface.OnClickListener() {
224 public void onClick(DialogInterface dialog, int which) {
225 Settings.Secure.putInt(getContentResolver(),
226 Settings.Secure.ACCESSIBILITY_ENABLED, 0);
227 setAccessibilityServicePreferencesState(false);
230 .setNegativeButton(android.R.string.cancel,
231 new DialogInterface.OnClickListener() {
232 public void onClick(DialogInterface dialog, int which) {
233 checkBoxPreference.setChecked(true);
242 * Handles the change of the preference for enabling/disabling an AccessibilityService.
244 * @param preference The preference.
246 private void handleEnableAccessibilityServiceStateChange(CheckBoxPreference preference) {
247 if (preference.isChecked()) {
248 final CheckBoxPreference checkBoxPreference = preference;
249 AlertDialog dialog = (new AlertDialog.Builder(this))
250 .setTitle(android.R.string.dialog_alert_title)
251 .setIcon(android.R.drawable.ic_dialog_alert)
252 .setMessage(getString(R.string.accessibility_service_security_warning,
253 mAccessibilityServices.get(preference.getKey())
254 .applicationInfo.loadLabel(getPackageManager())))
256 .setPositiveButton(android.R.string.ok,
257 new DialogInterface.OnClickListener() {
258 public void onClick(DialogInterface dialog, int which) {
259 checkBoxPreference.setChecked(true);
260 persistEnabledAccessibilityServices();
263 .setNegativeButton(android.R.string.cancel,
264 new DialogInterface.OnClickListener() {
265 public void onClick(DialogInterface dialog, int which) {
266 checkBoxPreference.setChecked(false);
272 persistEnabledAccessibilityServices();
277 * Persists the Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES setting.
278 * The AccessibilityManagerService watches this property and manages the
279 * AccessibilityServices.
281 private void persistEnabledAccessibilityServices() {
282 StringBuilder builder = new StringBuilder(256);
284 int firstEnabled = -1;
285 for (String key : mAccessibilityServices.keySet()) {
286 CheckBoxPreference preference = (CheckBoxPreference) findPreference(key);
287 if (preference.isChecked()) {
293 Settings.Secure.putString(getContentResolver(),
294 Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, builder.toString());
298 * Adds {@link CheckBoxPreference} for enabling or disabling an accessibility services.
300 private void addAccessibilitServicePreferences() {
301 AccessibilityManager accessibilityManager =
302 (AccessibilityManager) getSystemService(Service.ACCESSIBILITY_SERVICE);
304 List<ServiceInfo> installedServices = accessibilityManager.getAccessibilityServiceList();
306 mAccessibilityServicesCategory =
307 (PreferenceGroup) findPreference(ACCESSIBILITY_SERVICES_CATEGORY);
309 if (installedServices.isEmpty()) {
310 getPreferenceScreen().removePreference(mAccessibilityServicesCategory);
311 mAccessibilityServicesCategory = null;
315 for (int i = 0, count = installedServices.size(); i < count; ++i) {
316 ServiceInfo serviceInfo = installedServices.get(i);
317 String key = serviceInfo.packageName + "/" + serviceInfo.name;
319 mAccessibilityServices.put(key, serviceInfo);
321 CheckBoxPreference preference = new CheckBoxPreference(this);
322 preference.setKey(key);
323 preference.setTitle(serviceInfo.loadLabel(getPackageManager()));
324 mAccessibilityServicesCategory.addPreference(preference);
329 * Displays a message telling the user that they do not have any accessibility
330 * related apps installed and that they can get TalkBack (Google's free screen
331 * reader) from Market.
333 private void displayNoAppsAlert() {
335 PackageManager pm = getPackageManager();
336 ApplicationInfo info = pm.getApplicationInfo("com.android.vending", 0);
337 } catch (NameNotFoundException e) {
338 // This is a no-op if the user does not have Android Market
341 AlertDialog.Builder noAppsAlert = new AlertDialog.Builder(this);
342 noAppsAlert.setTitle(R.string.accessibility_service_no_apps_title);
343 noAppsAlert.setMessage(R.string.accessibility_service_no_apps_message);
345 noAppsAlert.setPositiveButton(android.R.string.ok,
346 new DialogInterface.OnClickListener() {
347 public void onClick(DialogInterface dialog, int which) {
348 String screenreaderMarketLink =
349 SystemProperties.get("ro.screenreader.market",
350 DEFAULT_SCREENREADER_MARKET_LINK);
351 Uri marketUri = Uri.parse(screenreaderMarketLink);
352 Intent marketIntent = new Intent(Intent.ACTION_VIEW, marketUri);
353 startActivity(marketIntent);
358 noAppsAlert.setNegativeButton(android.R.string.cancel,
359 new DialogInterface.OnClickListener() {
360 public void onClick(DialogInterface dialog, int which) {