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.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;
65 import com.android.internal.content.PackageMonitor;
66 import com.android.settings.AccessibilitySettings.ToggleSwitch.OnBeforeCheckedChangeListener;
68 import java.util.HashMap;
69 import java.util.HashSet;
70 import java.util.List;
75 * Activity with the accessibility settings.
77 public class AccessibilitySettings extends SettingsPreferenceFragment implements DialogCreatable,
78 Preference.OnPreferenceChangeListener {
79 private static final String TAG = "AccessibilitySettings";
81 private static final String DEFAULT_SCREENREADER_MARKET_LINK =
82 "market://search?q=pname:com.google.android.marvin.talkback";
84 private static final float LARGE_FONT_SCALE = 1.3f;
86 private static final String SYSTEM_PROPERTY_MARKET_URL = "ro.screenreader.market";
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;
93 private static final char ENABLED_ACCESSIBILITY_SERVICES_SEPARATOR = ':';
95 private static final String KEY_ACCESSIBILITY_TUTORIAL_LAUNCHED_ONCE =
96 "key_accessibility_tutorial_launched_once";
98 private static final String KEY_INSTALL_ACCESSIBILITY_SERVICE_OFFERED_ONCE =
99 "key_install_accessibility_service_offered_once";
101 // Preference categories
102 private static final String SERVICES_CATEGORY = "services_category";
103 private static final String SYSTEM_CATEGORY = "system_category";
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_SPEAK_PASSWORD_PREFERENCE =
112 "toggle_speak_password_preference";
113 private static final String TOGGLE_TOUCH_EXPLORATION_PREFERENCE =
114 "toggle_touch_exploration_preference";
115 private static final String SELECT_LONG_PRESS_TIMEOUT_PREFERENCE =
116 "select_long_press_timeout_preference";
117 private static final String TOGGLE_SCRIPT_INJECTION_PREFERENCE =
118 "toggle_script_injection_preference";
120 // Extras passed to sub-fragments.
121 private static final String EXTRA_PREFERENCE_KEY = "preference_key";
122 private static final String EXTRA_CHECKED = "checked";
123 private static final String EXTRA_TITLE = "title";
124 private static final String EXTRA_SUMMARY = "summary";
125 private static final String EXTRA_ENABLE_WARNING_TITLE = "enable_warning_title";
126 private static final String EXTRA_ENABLE_WARNING_MESSAGE = "enable_warning_message";
127 private static final String EXTRA_DISABLE_WARNING_TITLE = "disable_warning_title";
128 private static final String EXTRA_DISABLE_WARNING_MESSAGE = "disable_warning_message";
129 private static final String EXTRA_SETTINGS_TITLE = "settings_title";
130 private static final String EXTRA_SETTINGS_COMPONENT_NAME = "settings_component_name";
133 private static final int DIALOG_ID_NO_ACCESSIBILITY_SERVICES = 1;
135 // Auxiliary members.
136 private final static SimpleStringSplitter sStringColonSplitter =
137 new SimpleStringSplitter(ENABLED_ACCESSIBILITY_SERVICES_SEPARATOR);
139 private static final Set<ComponentName> sInstalledServices = new HashSet<ComponentName>();
141 private final Map<String, String> mLongPressTimeoutValuetoTitleMap =
142 new HashMap<String, String>();
144 private final Configuration mCurConfig = new Configuration();
146 private final PackageMonitor mSettingsPackageMonitor = new SettingsPackageMonitor();
148 private final Handler mHandler = new Handler() {
150 public void dispatchMessage(Message msg) {
151 super.dispatchMessage(msg);
152 loadInstalledServices();
153 updateServicesPreferences();
157 // Preference controls.
158 private PreferenceCategory mServicesCategory;
159 private PreferenceCategory mSystemsCategory;
161 private CheckBoxPreference mToggleLargeTextPreference;
162 private CheckBoxPreference mTogglePowerButtonEndsCallPreference;
163 private CheckBoxPreference mToggleAutoRotateScreenPreference;
164 private CheckBoxPreference mToggleSpeakPasswordPreference;
165 private Preference mToggleTouchExplorationPreference;
166 private ListPreference mSelectLongPressTimeoutPreference;
167 private AccessibilityEnableScriptInjectionPreference mToggleScriptInjectionPreference;
168 private Preference mNoServicesMessagePreference;
170 private int mLongPressTimeoutDefault;
173 public void onCreate(Bundle icicle) {
174 super.onCreate(icicle);
175 addPreferencesFromResource(R.xml.accessibility_settings);
176 initializeAllPreferences();
180 public void onResume() {
182 loadInstalledServices();
183 updateAllPreferences();
184 if (mServicesCategory.getPreference(0) == mNoServicesMessagePreference) {
185 offerInstallAccessibilitySerivceOnce();
187 mSettingsPackageMonitor.register(getActivity(), false);
191 public void onPause() {
192 mSettingsPackageMonitor.unregister();
196 public boolean onPreferenceChange(Preference preference, Object newValue) {
197 if (preference == mSelectLongPressTimeoutPreference) {
198 String stringValue = (String) newValue;
199 Settings.Secure.putInt(getContentResolver(),
200 Settings.Secure.LONG_PRESS_TIMEOUT, Integer.parseInt(stringValue));
201 mSelectLongPressTimeoutPreference.setSummary(
202 mLongPressTimeoutValuetoTitleMap.get(stringValue));
209 public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) {
210 if (mToggleLargeTextPreference == preference) {
211 handleToggleLargeTextPreferenceClick();
213 } else if (mTogglePowerButtonEndsCallPreference == preference) {
214 handleTogglePowerButtonEndsCallPreferenceClick();
216 } else if (mToggleAutoRotateScreenPreference == preference) {
217 handleToggleAutoRotateScreenPreferenceClick();
219 } else if (mToggleSpeakPasswordPreference == preference) {
220 handleToggleSpeakPasswordPreferenceClick();
222 return super.onPreferenceTreeClick(preferenceScreen, preference);
225 private void handleToggleLargeTextPreferenceClick() {
227 mCurConfig.fontScale = mToggleLargeTextPreference.isChecked() ? LARGE_FONT_SCALE : 1;
228 ActivityManagerNative.getDefault().updatePersistentConfiguration(mCurConfig);
229 } catch (RemoteException re) {
234 private void handleTogglePowerButtonEndsCallPreferenceClick() {
235 Settings.Secure.putInt(getContentResolver(),
236 Settings.Secure.INCALL_POWER_BUTTON_BEHAVIOR,
237 (mTogglePowerButtonEndsCallPreference.isChecked()
238 ? Settings.Secure.INCALL_POWER_BUTTON_BEHAVIOR_HANGUP
239 : Settings.Secure.INCALL_POWER_BUTTON_BEHAVIOR_SCREEN_OFF));
242 private void handleToggleAutoRotateScreenPreferenceClick() {
244 IWindowManager wm = IWindowManager.Stub.asInterface(
245 ServiceManager.getService(Context.WINDOW_SERVICE));
246 if (mToggleAutoRotateScreenPreference.isChecked()) {
249 wm.freezeRotation(Surface.ROTATION_0);
251 } catch (RemoteException exc) {
252 Log.w(TAG, "Unable to save auto-rotate setting");
256 private void handleToggleSpeakPasswordPreferenceClick() {
257 Settings.Secure.putInt(getContentResolver(),
258 Settings.Secure.ACCESSIBILITY_SPEAK_PASSWORD,
259 mToggleSpeakPasswordPreference.isChecked() ? 1 : 0);
262 private void initializeAllPreferences() {
263 mServicesCategory = (PreferenceCategory) findPreference(SERVICES_CATEGORY);
264 mSystemsCategory = (PreferenceCategory) findPreference(SYSTEM_CATEGORY);
267 mToggleLargeTextPreference =
268 (CheckBoxPreference) findPreference(TOGGLE_LARGE_TEXT_PREFERENCE);
270 // Power button ends calls.
271 mTogglePowerButtonEndsCallPreference =
272 (CheckBoxPreference) findPreference(TOGGLE_POWER_BUTTON_ENDS_CALL_PREFERENCE);
273 if (!KeyCharacterMap.deviceHasKey(KeyEvent.KEYCODE_POWER)
274 || !Utils.isVoiceCapable(getActivity())) {
275 mSystemsCategory.removePreference(mTogglePowerButtonEndsCallPreference);
278 // Auto-rotate screen
279 mToggleAutoRotateScreenPreference =
280 (CheckBoxPreference) findPreference(TOGGLE_AUTO_ROTATE_SCREEN_PREFERENCE);
283 mToggleSpeakPasswordPreference =
284 (CheckBoxPreference) findPreference(TOGGLE_SPEAK_PASSWORD_PREFERENCE);
286 // Touch exploration enabled.
287 mToggleTouchExplorationPreference = findPreference(TOGGLE_TOUCH_EXPLORATION_PREFERENCE);
289 // Long press timeout.
290 mSelectLongPressTimeoutPreference =
291 (ListPreference) findPreference(SELECT_LONG_PRESS_TIMEOUT_PREFERENCE);
292 mSelectLongPressTimeoutPreference.setOnPreferenceChangeListener(this);
293 if (mLongPressTimeoutValuetoTitleMap.size() == 0) {
294 String[] timeoutValues = getResources().getStringArray(
295 R.array.long_press_timeout_selector_values);
296 mLongPressTimeoutDefault = Integer.parseInt(timeoutValues[0]);
297 String[] timeoutTitles = getResources().getStringArray(
298 R.array.long_press_timeout_selector_titles);
299 final int timeoutValueCount = timeoutValues.length;
300 for (int i = 0; i < timeoutValueCount; i++) {
301 mLongPressTimeoutValuetoTitleMap.put(timeoutValues[i], timeoutTitles[i]);
306 mToggleScriptInjectionPreference = (AccessibilityEnableScriptInjectionPreference)
307 findPreference(TOGGLE_SCRIPT_INJECTION_PREFERENCE);
310 private void updateAllPreferences() {
311 updateServicesPreferences();
312 updateSystemPreferences();
315 private void updateServicesPreferences() {
316 // Since services category is auto generated we have to do a pass
317 // to generate it since services can come and go and then based on
318 // the global accessibility state to decided whether it is enabled.
321 mServicesCategory.removeAll();
323 AccessibilityManager accessibilityManager = AccessibilityManager.getInstance(getActivity());
325 List<AccessibilityServiceInfo> installedServices =
326 accessibilityManager.getInstalledAccessibilityServiceList();
327 Set<ComponentName> enabledServices = getEnabledServicesFromSettings(getActivity());
329 final boolean accessibilityEnabled = Settings.Secure.getInt(getContentResolver(),
330 Settings.Secure.ACCESSIBILITY_ENABLED, 0) == 1;
332 for (int i = 0, count = installedServices.size(); i < count; ++i) {
333 AccessibilityServiceInfo info = installedServices.get(i);
335 PreferenceScreen preference = getPreferenceManager().createPreferenceScreen(
337 String title = info.getResolveInfo().loadLabel(getPackageManager()).toString();
339 ServiceInfo serviceInfo = info.getResolveInfo().serviceInfo;
340 ComponentName componentName = new ComponentName(serviceInfo.packageName,
343 preference.setKey(componentName.flattenToString());
345 preference.setTitle(title);
346 final boolean serviceEnabled = accessibilityEnabled
347 && enabledServices.contains(componentName);
348 if (serviceEnabled) {
349 preference.setSummary(getString(R.string.accessibility_service_state_on));
351 preference.setSummary(getString(R.string.accessibility_service_state_off));
354 preference.setOrder(i);
355 preference.setFragment(ToggleAccessibilityServiceFragment.class.getName());
356 preference.setPersistent(true);
358 Bundle extras = preference.getExtras();
359 extras.putString(EXTRA_PREFERENCE_KEY, preference.getKey());
360 extras.putBoolean(EXTRA_CHECKED, serviceEnabled);
361 extras.putString(EXTRA_TITLE, title);
363 String description = info.getDescription();
364 if (TextUtils.isEmpty(description)) {
365 description = getString(R.string.accessibility_service_default_description);
367 extras.putString(EXTRA_SUMMARY, description);
369 CharSequence applicationLabel = info.getResolveInfo().loadLabel(getPackageManager());
371 extras.putString(EXTRA_ENABLE_WARNING_TITLE, getString(
372 R.string.accessibility_service_security_warning_title, applicationLabel));
373 extras.putString(EXTRA_ENABLE_WARNING_MESSAGE, getString(
374 R.string.accessibility_service_security_warning_summary, applicationLabel));
376 extras.putString(EXTRA_DISABLE_WARNING_TITLE, getString(
377 R.string.accessibility_service_disable_warning_title,
379 extras.putString(EXTRA_DISABLE_WARNING_MESSAGE, getString(
380 R.string.accessibility_service_disable_warning_summary,
383 String settingsClassName = info.getSettingsActivityName();
384 if (!TextUtils.isEmpty(settingsClassName)) {
385 extras.putString(EXTRA_SETTINGS_TITLE,
386 getString(R.string.accessibility_menu_item_settings));
387 extras.putString(EXTRA_SETTINGS_COMPONENT_NAME,
388 new ComponentName(info.getResolveInfo().serviceInfo.packageName,
389 settingsClassName).flattenToString());
392 mServicesCategory.addPreference(preference);
395 if (mServicesCategory.getPreferenceCount() == 0) {
396 if (mNoServicesMessagePreference == null) {
397 mNoServicesMessagePreference = new Preference(getActivity()) {
399 protected void onBindView(View view) {
400 super.onBindView(view);
402 LinearLayout containerView =
403 (LinearLayout) view.findViewById(R.id.message_container);
404 containerView.setGravity(Gravity.CENTER);
406 TextView summaryView = (TextView) view.findViewById(R.id.summary);
407 String title = getString(R.string.accessibility_no_services_installed);
408 summaryView.setText(title);
411 mNoServicesMessagePreference.setPersistent(false);
412 mNoServicesMessagePreference.setLayoutResource(
413 R.layout.text_description_preference);
414 mNoServicesMessagePreference.setSelectable(false);
416 mServicesCategory.addPreference(mNoServicesMessagePreference);
420 private void updateSystemPreferences() {
423 mCurConfig.updateFrom(ActivityManagerNative.getDefault().getConfiguration());
424 } catch (RemoteException re) {
427 mToggleLargeTextPreference.setChecked(mCurConfig.fontScale == LARGE_FONT_SCALE);
429 // Power button ends calls.
430 if (KeyCharacterMap.deviceHasKey(KeyEvent.KEYCODE_POWER)
431 && Utils.isVoiceCapable(getActivity())) {
432 final int incallPowerBehavior = Settings.Secure.getInt(getContentResolver(),
433 Settings.Secure.INCALL_POWER_BUTTON_BEHAVIOR,
434 Settings.Secure.INCALL_POWER_BUTTON_BEHAVIOR_DEFAULT);
435 final boolean powerButtonEndsCall =
436 (incallPowerBehavior == Settings.Secure.INCALL_POWER_BUTTON_BEHAVIOR_HANGUP);
437 mTogglePowerButtonEndsCallPreference.setChecked(powerButtonEndsCall);
440 // Auto-rotate screen
441 final boolean autoRotationEnabled = Settings.System.getInt(getContentResolver(),
442 Settings.System.ACCELEROMETER_ROTATION, 0) != 0;
443 mToggleAutoRotateScreenPreference.setChecked(autoRotationEnabled);
446 final boolean speakPasswordEnabled = Settings.Secure.getInt(getContentResolver(),
447 Settings.Secure.ACCESSIBILITY_SPEAK_PASSWORD, 0) != 0;
448 mToggleSpeakPasswordPreference.setChecked(speakPasswordEnabled);
450 // Touch exploration enabled.
451 if (AccessibilityManager.getInstance(getActivity()).isEnabled()) {
452 mSystemsCategory.addPreference(mToggleTouchExplorationPreference);
453 final boolean touchExplorationEnabled = (Settings.Secure.getInt(getContentResolver(),
454 Settings.Secure.TOUCH_EXPLORATION_ENABLED, 0) == 1);
455 if (touchExplorationEnabled) {
456 mToggleTouchExplorationPreference.setSummary(
457 getString(R.string.accessibility_service_state_on));
458 mToggleTouchExplorationPreference.getExtras().putBoolean(EXTRA_CHECKED, true);
460 mToggleTouchExplorationPreference.setSummary(
461 getString(R.string.accessibility_service_state_off));
462 mToggleTouchExplorationPreference.getExtras().putBoolean(EXTRA_CHECKED, false);
466 mSystemsCategory.removePreference(mToggleTouchExplorationPreference);
469 // Long press timeout.
470 final int longPressTimeout = Settings.Secure.getInt(getContentResolver(),
471 Settings.Secure.LONG_PRESS_TIMEOUT, mLongPressTimeoutDefault);
472 String value = String.valueOf(longPressTimeout);
473 mSelectLongPressTimeoutPreference.setValue(value);
474 mSelectLongPressTimeoutPreference.setSummary(mLongPressTimeoutValuetoTitleMap.get(value));
477 final boolean scriptInjectionAllowed = (Settings.Secure.getInt(getContentResolver(),
478 Settings.Secure.ACCESSIBILITY_SCRIPT_INJECTION, 0) == 1);
479 mToggleScriptInjectionPreference.setInjectionAllowed(scriptInjectionAllowed);
482 private void offerInstallAccessibilitySerivceOnce() {
483 // There is always one preference - if no services it is just a message.
484 if (mServicesCategory.getPreference(0) != mNoServicesMessagePreference) {
487 SharedPreferences preferences = getActivity().getPreferences(Context.MODE_PRIVATE);
488 final boolean offerInstallService = !preferences.getBoolean(
489 KEY_INSTALL_ACCESSIBILITY_SERVICE_OFFERED_ONCE, false);
490 if (offerInstallService) {
491 preferences.edit().putBoolean(KEY_INSTALL_ACCESSIBILITY_SERVICE_OFFERED_ONCE,
493 // Notify user that they do not have any accessibility
494 // services installed and direct them to Market to get TalkBack.
495 showDialog(DIALOG_ID_NO_ACCESSIBILITY_SERVICES);
500 public Dialog onCreateDialog(int dialogId) {
502 case DIALOG_ID_NO_ACCESSIBILITY_SERVICES:
503 return new AlertDialog.Builder(getActivity())
504 .setTitle(R.string.accessibility_service_no_apps_title)
505 .setMessage(R.string.accessibility_service_no_apps_message)
506 .setPositiveButton(android.R.string.ok,
507 new DialogInterface.OnClickListener() {
508 public void onClick(DialogInterface dialog, int which) {
509 // dismiss the dialog before launching the activity otherwise
510 // the dialog removal occurs after onSaveInstanceState which
511 // triggers an exception
512 removeDialog(DIALOG_ID_NO_ACCESSIBILITY_SERVICES);
513 String screenreaderMarketLink = SystemProperties.get(
514 SYSTEM_PROPERTY_MARKET_URL,
515 DEFAULT_SCREENREADER_MARKET_LINK);
516 Uri marketUri = Uri.parse(screenreaderMarketLink);
517 Intent marketIntent = new Intent(Intent.ACTION_VIEW, marketUri);
518 startActivity(marketIntent);
521 .setNegativeButton(android.R.string.cancel, null)
528 private void loadInstalledServices() {
529 List<AccessibilityServiceInfo> installedServiceInfos =
530 AccessibilityManager.getInstance(getActivity())
531 .getInstalledAccessibilityServiceList();
532 Set<ComponentName> installedServices = sInstalledServices;
533 installedServices.clear();
534 final int installedServiceInfoCount = installedServiceInfos.size();
535 for (int i = 0; i < installedServiceInfoCount; i++) {
536 ResolveInfo resolveInfo = installedServiceInfos.get(i).getResolveInfo();
537 ComponentName installedService = new ComponentName(
538 resolveInfo.serviceInfo.packageName,
539 resolveInfo.serviceInfo.name);
540 installedServices.add(installedService);
544 private static Set<ComponentName> getEnabledServicesFromSettings(Context context) {
545 String enabledServicesSetting = Settings.Secure.getString(context.getContentResolver(),
546 Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES);
547 if (enabledServicesSetting == null) {
548 enabledServicesSetting = "";
550 Set<ComponentName> enabledServices = new HashSet<ComponentName>();
551 SimpleStringSplitter colonSplitter = sStringColonSplitter;
552 colonSplitter.setString(enabledServicesSetting);
553 while (colonSplitter.hasNext()) {
554 String componentNameString = colonSplitter.next();
555 ComponentName enabledService = ComponentName.unflattenFromString(
556 componentNameString);
557 if (enabledService != null) {
558 enabledServices.add(enabledService);
561 return enabledServices;
564 private class SettingsPackageMonitor extends PackageMonitor {
567 public void onPackageAdded(String packageName, int uid) {
568 Message message = mHandler.obtainMessage();
569 mHandler.sendMessageDelayed(message, DELAY_UPDATE_SERVICES_MILLIS);
573 public void onPackageAppeared(String packageName, int reason) {
574 Message message = mHandler.obtainMessage();
575 mHandler.sendMessageDelayed(message, DELAY_UPDATE_SERVICES_MILLIS);
579 public void onPackageDisappeared(String packageName, int reason) {
580 Message message = mHandler.obtainMessage();
581 mHandler.sendMessageDelayed(message, DELAY_UPDATE_SERVICES_MILLIS);
585 public void onPackageRemoved(String packageName, int uid) {
586 Message message = mHandler.obtainMessage();
587 mHandler.sendMessageDelayed(message, DELAY_UPDATE_SERVICES_MILLIS);
591 private static ToggleSwitch createAndAddActionBarToggleSwitch(Activity activity) {
592 ToggleSwitch toggleSwitch = new ToggleSwitch(activity);
593 final int padding = activity.getResources().getDimensionPixelSize(
594 R.dimen.action_bar_switch_padding);
595 toggleSwitch.setPadding(0, 0, padding, 0);
596 activity.getActionBar().setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM,
597 ActionBar.DISPLAY_SHOW_CUSTOM);
598 activity.getActionBar().setCustomView(toggleSwitch,
599 new ActionBar.LayoutParams(ActionBar.LayoutParams.WRAP_CONTENT,
600 ActionBar.LayoutParams.WRAP_CONTENT,
601 Gravity.CENTER_VERTICAL | Gravity.RIGHT));
605 public static class ToggleSwitch extends Switch {
607 private OnBeforeCheckedChangeListener mOnBeforeListener;
609 public static interface OnBeforeCheckedChangeListener {
610 public boolean onBeforeCheckedChanged(ToggleSwitch toggleSwitch, boolean checked);
613 public ToggleSwitch(Context context) {
617 public void setOnBeforeCheckedChangeListener(OnBeforeCheckedChangeListener listener) {
618 mOnBeforeListener = listener;
622 public void setChecked(boolean checked) {
623 if (mOnBeforeListener != null
624 && mOnBeforeListener.onBeforeCheckedChanged(this, checked)) {
627 super.setChecked(checked);
630 public void setCheckedInternal(boolean checked) {
631 super.setChecked(checked);
635 public static class ToggleAccessibilityServiceFragment extends TogglePreferenceFragment {
637 public void onPreferenceToggled(String preferenceKey, boolean enabled) {
638 // Parse the enabled services.
639 Set<ComponentName> enabledServices = getEnabledServicesFromSettings(getActivity());
641 // Determine enabled services and accessibility state.
642 ComponentName toggledService = ComponentName.unflattenFromString(preferenceKey);
643 final boolean accessibilityEnabled;
645 // Enabling at least one service enables accessibility.
646 accessibilityEnabled = true;
647 enabledServices.add(toggledService);
649 // Check how many enabled and installed services are present.
650 int enabledAndInstalledServiceCount = 0;
651 Set<ComponentName> installedServices = sInstalledServices;
652 for (ComponentName enabledService : enabledServices) {
653 if (installedServices.contains(enabledService)) {
654 enabledAndInstalledServiceCount++;
657 // Disabling the last service disables accessibility.
658 accessibilityEnabled = enabledAndInstalledServiceCount > 1
659 || (enabledAndInstalledServiceCount == 1
660 && !installedServices.contains(toggledService));
661 enabledServices.remove(toggledService);
664 // Update the enabled services setting.
665 StringBuilder enabledServicesBuilder = new StringBuilder();
666 // Keep the enabled services even if they are not installed since we have
667 // no way to know whether the application restore process has completed.
668 // In general the system should be responsible for the clean up not settings.
669 for (ComponentName enabledService : enabledServices) {
670 enabledServicesBuilder.append(enabledService.flattenToString());
671 enabledServicesBuilder.append(ENABLED_ACCESSIBILITY_SERVICES_SEPARATOR);
673 final int enabledServicesBuilderLength = enabledServicesBuilder.length();
674 if (enabledServicesBuilderLength > 0) {
675 enabledServicesBuilder.deleteCharAt(enabledServicesBuilderLength - 1);
677 Settings.Secure.putString(getContentResolver(),
678 Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
679 enabledServicesBuilder.toString());
681 // Update accessibility enabled.
682 Settings.Secure.putInt(getContentResolver(),
683 Settings.Secure.ACCESSIBILITY_ENABLED, accessibilityEnabled ? 1 : 0);
687 public static class ToggleTouchExplorationFragment extends TogglePreferenceFragment {
689 public void onPreferenceToggled(String preferenceKey, boolean enabled) {
690 Settings.Secure.putInt(getContentResolver(),
691 Settings.Secure.TOUCH_EXPLORATION_ENABLED, enabled ? 1 : 0);
693 SharedPreferences preferences = getActivity().getPreferences(Context.MODE_PRIVATE);
694 final boolean launchAccessibilityTutorial = !preferences.getBoolean(
695 KEY_ACCESSIBILITY_TUTORIAL_LAUNCHED_ONCE, false);
696 if (launchAccessibilityTutorial) {
697 preferences.edit().putBoolean(KEY_ACCESSIBILITY_TUTORIAL_LAUNCHED_ONCE,
699 Intent intent = new Intent(AccessibilityTutorialActivity.ACTION);
700 getActivity().startActivity(intent);
706 private abstract static class TogglePreferenceFragment extends SettingsPreferenceFragment
707 implements DialogInterface.OnClickListener {
709 private static final int DIALOG_ID_ENABLE_WARNING = 1;
710 private static final int DIALOG_ID_DISABLE_WARNING = 2;
712 private String mPreferenceKey;
714 private ToggleSwitch mToggleSwitch;
716 private CharSequence mEnableWarningTitle;
717 private CharSequence mEnableWarningMessage;
718 private CharSequence mDisableWarningTitle;
719 private CharSequence mDisableWarningMessage;
720 private Preference mSummaryPreference;
722 private CharSequence mSettingsTitle;
723 private Intent mSettingsIntent;
725 private int mShownDialogId;
727 // TODO: Showing sub-sub fragment does not handle the activity title
728 // so we do it but this is wrong. Do a real fix when there is time.
729 private CharSequence mOldActivityTitle;
732 public void onCreate(Bundle savedInstanceState) {
733 super.onCreate(savedInstanceState);
734 PreferenceScreen preferenceScreen = getPreferenceManager().createPreferenceScreen(
736 setPreferenceScreen(preferenceScreen);
737 mSummaryPreference = new Preference(getActivity()) {
739 protected void onBindView(View view) {
740 super.onBindView(view);
741 TextView summaryView = (TextView) view.findViewById(R.id.summary);
742 summaryView.setText(getSummary());
743 sendAccessibilityEvent(summaryView);
746 private void sendAccessibilityEvent(View view) {
747 // Since the view is still not attached we create, populate,
748 // and send the event directly since we do not know when it
749 // will be attached and posting commands is not as clean.
750 AccessibilityManager accessibilityManager =
751 AccessibilityManager.getInstance(getActivity());
752 if (accessibilityManager.isEnabled()) {
753 AccessibilityEvent event = AccessibilityEvent.obtain();
754 event.setEventType(AccessibilityEvent.TYPE_VIEW_FOCUSED);
755 view.onInitializeAccessibilityEvent(event);
756 view.dispatchPopulateAccessibilityEvent(event);
757 accessibilityManager.sendAccessibilityEvent(event);
761 mSummaryPreference.setPersistent(false);
762 mSummaryPreference.setLayoutResource(R.layout.text_description_preference);
763 preferenceScreen.addPreference(mSummaryPreference);
767 public void onViewCreated(View view, Bundle savedInstanceState) {
768 super.onViewCreated(view, savedInstanceState);
769 installActionBarToggleSwitch();
771 getListView().setDivider(null);
772 getListView().setEnabled(false);
776 public void onDestroyView() {
777 getActivity().getActionBar().setCustomView(null);
778 if (mOldActivityTitle != null) {
779 getActivity().getActionBar().setTitle(mOldActivityTitle);
781 mToggleSwitch.setOnBeforeCheckedChangeListener(null);
782 super.onDestroyView();
785 public abstract void onPreferenceToggled(String preferenceKey, boolean value);
788 public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
789 super.onCreateOptionsMenu(menu, inflater);
790 MenuItem menuItem = menu.add(mSettingsTitle);
791 menuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
792 menuItem.setIntent(mSettingsIntent);
796 public Dialog onCreateDialog(int dialogId) {
797 CharSequence title = null;
798 CharSequence message = null;
800 case DIALOG_ID_ENABLE_WARNING:
801 mShownDialogId = DIALOG_ID_ENABLE_WARNING;
802 title = mEnableWarningTitle;
803 message = mEnableWarningMessage;
805 case DIALOG_ID_DISABLE_WARNING:
806 mShownDialogId = DIALOG_ID_DISABLE_WARNING;
807 title = mDisableWarningTitle;
808 message = mDisableWarningMessage;
811 throw new IllegalArgumentException();
813 return new AlertDialog.Builder(getActivity())
815 .setIcon(android.R.drawable.ic_dialog_alert)
818 .setPositiveButton(android.R.string.ok, this)
819 .setNegativeButton(android.R.string.cancel, this)
824 public void onClick(DialogInterface dialog, int which) {
825 final boolean checked;
827 case DialogInterface.BUTTON_POSITIVE:
828 checked = (mShownDialogId == DIALOG_ID_ENABLE_WARNING);
829 mToggleSwitch.setCheckedInternal(checked);
830 getArguments().putBoolean(EXTRA_CHECKED, checked);
831 onPreferenceToggled(mPreferenceKey, checked);
833 case DialogInterface.BUTTON_NEGATIVE:
834 checked = (mShownDialogId == DIALOG_ID_DISABLE_WARNING);
835 mToggleSwitch.setCheckedInternal(checked);
836 getArguments().putBoolean(EXTRA_CHECKED, checked);
837 onPreferenceToggled(mPreferenceKey, checked);
840 throw new IllegalArgumentException();
844 private void installActionBarToggleSwitch() {
845 mToggleSwitch = createAndAddActionBarToggleSwitch(getActivity());
846 mToggleSwitch.setOnBeforeCheckedChangeListener(new OnBeforeCheckedChangeListener() {
848 public boolean onBeforeCheckedChanged(ToggleSwitch toggleSwitch, boolean checked) {
850 if (!TextUtils.isEmpty(mEnableWarningMessage)) {
851 toggleSwitch.setCheckedInternal(false);
852 getArguments().putBoolean(EXTRA_CHECKED, false);
853 showDialog(DIALOG_ID_ENABLE_WARNING);
856 onPreferenceToggled(mPreferenceKey, true);
858 if (!TextUtils.isEmpty(mDisableWarningMessage)) {
859 toggleSwitch.setCheckedInternal(true);
860 getArguments().putBoolean(EXTRA_CHECKED, true);
861 showDialog(DIALOG_ID_DISABLE_WARNING);
864 onPreferenceToggled(mPreferenceKey, false);
871 private void processArguments() {
872 Bundle arguments = getArguments();
875 mPreferenceKey = arguments.getString(EXTRA_PREFERENCE_KEY);
878 final boolean enabled = arguments.getBoolean(EXTRA_CHECKED);
879 mToggleSwitch.setCheckedInternal(enabled);
882 PreferenceActivity activity = (PreferenceActivity) getActivity();
883 if (!activity.onIsMultiPane() || activity.onIsHidingHeaders()) {
884 mOldActivityTitle = getActivity().getTitle();
885 String title = arguments.getString(EXTRA_TITLE);
886 getActivity().getActionBar().setTitle(title);
890 String summary = arguments.getString(EXTRA_SUMMARY);
891 mSummaryPreference.setSummary(summary);
893 // Settings title and intent.
894 String settingsTitle = arguments.getString(EXTRA_SETTINGS_TITLE);
895 String settingsComponentName = arguments.getString(EXTRA_SETTINGS_COMPONENT_NAME);
896 if (!TextUtils.isEmpty(settingsTitle) && !TextUtils.isEmpty(settingsComponentName)) {
897 Intent settingsIntent = new Intent(Intent.ACTION_MAIN).setComponent(
898 ComponentName.unflattenFromString(settingsComponentName.toString()));
899 if (!getPackageManager().queryIntentActivities(settingsIntent, 0).isEmpty()) {
900 mSettingsTitle = settingsTitle;
901 mSettingsIntent = settingsIntent;
902 setHasOptionsMenu(true);
906 // Enable warning title.
907 mEnableWarningTitle = arguments.getCharSequence(
908 AccessibilitySettings.EXTRA_ENABLE_WARNING_TITLE);
910 // Enable warning message.
911 mEnableWarningMessage = arguments.getCharSequence(
912 AccessibilitySettings.EXTRA_ENABLE_WARNING_MESSAGE);
914 // Disable warning title.
915 mDisableWarningTitle = arguments.getString(
916 AccessibilitySettings.EXTRA_DISABLE_WARNING_TITLE);
918 // Disable warning message.
919 mDisableWarningMessage = arguments.getString(
920 AccessibilitySettings.EXTRA_DISABLE_WARNING_MESSAGE);