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.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.ServiceManager;
40 import android.os.SystemProperties;
41 import android.preference.CheckBoxPreference;
42 import android.preference.ListPreference;
43 import android.preference.Preference;
44 import android.preference.PreferenceActivity;
45 import android.preference.PreferenceCategory;
46 import android.preference.PreferenceScreen;
47 import android.provider.Settings;
48 import android.provider.Settings.SettingNotFoundException;
49 import android.text.TextUtils;
50 import android.text.TextUtils.SimpleStringSplitter;
51 import android.util.Log;
52 import android.view.Gravity;
53 import android.view.IWindowManager;
54 import android.view.KeyCharacterMap;
55 import android.view.KeyEvent;
56 import android.view.Menu;
57 import android.view.MenuInflater;
58 import android.view.MenuItem;
59 import android.view.Surface;
60 import android.view.View;
61 import android.view.accessibility.AccessibilityEvent;
62 import android.view.accessibility.AccessibilityManager;
63 import android.widget.LinearLayout;
64 import android.widget.Switch;
65 import android.widget.TextView;
67 import com.android.internal.content.PackageMonitor;
68 import com.android.settings.AccessibilitySettings.ToggleSwitch.OnBeforeCheckedChangeListener;
70 import java.util.HashMap;
71 import java.util.HashSet;
72 import java.util.List;
77 * Activity with the accessibility settings.
79 public class AccessibilitySettings extends SettingsPreferenceFragment implements DialogCreatable,
80 Preference.OnPreferenceChangeListener {
81 private static final String TAG = "AccessibilitySettings";
83 private static final String DEFAULT_SCREENREADER_MARKET_LINK =
84 "market://search?q=pname:com.google.android.marvin.talkback";
86 private static final float LARGE_FONT_SCALE = 1.3f;
88 private static final String SYSTEM_PROPERTY_MARKET_URL = "ro.screenreader.market";
90 // Timeout before we update the services if packages are added/removed since
91 // the AccessibilityManagerService has to do that processing first to generate
92 // the AccessibilityServiceInfo we need for proper presentation.
93 private static final long DELAY_UPDATE_SERVICES_MILLIS = 1000;
95 private static final char ENABLED_ACCESSIBILITY_SERVICES_SEPARATOR = ':';
97 private static final String KEY_INSTALL_ACCESSIBILITY_SERVICE_OFFERED_ONCE =
98 "key_install_accessibility_service_offered_once";
100 // Preference categories
101 private static final String SERVICES_CATEGORY = "services_category";
102 private static final String SYSTEM_CATEGORY = "system_category";
105 private static final String TOGGLE_LARGE_TEXT_PREFERENCE = "toggle_large_text_preference";
106 private static final String TOGGLE_POWER_BUTTON_ENDS_CALL_PREFERENCE =
107 "toggle_power_button_ends_call_preference";
108 private static final String TOGGLE_LOCK_SCREEN_ROTATION_PREFERENCE =
109 "toggle_lock_screen_rotation_preference";
110 private static final String TOGGLE_SPEAK_PASSWORD_PREFERENCE =
111 "toggle_speak_password_preference";
112 private static final String SELECT_LONG_PRESS_TIMEOUT_PREFERENCE =
113 "select_long_press_timeout_preference";
114 private static final String TOGGLE_SCRIPT_INJECTION_PREFERENCE =
115 "toggle_script_injection_preference";
117 // Extras passed to sub-fragments.
118 private static final String EXTRA_PREFERENCE_KEY = "preference_key";
119 private static final String EXTRA_CHECKED = "checked";
120 private static final String EXTRA_TITLE = "title";
121 private static final String EXTRA_SUMMARY = "summary";
122 private static final String EXTRA_ENABLE_WARNING_TITLE = "enable_warning_title";
123 private static final String EXTRA_ENABLE_WARNING_MESSAGE = "enable_warning_message";
124 private static final String EXTRA_DISABLE_WARNING_TITLE = "disable_warning_title";
125 private static final String EXTRA_DISABLE_WARNING_MESSAGE = "disable_warning_message";
126 private static final String EXTRA_SETTINGS_TITLE = "settings_title";
127 private static final String EXTRA_SETTINGS_COMPONENT_NAME = "settings_component_name";
130 private static final int DIALOG_ID_NO_ACCESSIBILITY_SERVICES = 1;
132 // Auxiliary members.
133 private final static SimpleStringSplitter sStringColonSplitter =
134 new SimpleStringSplitter(ENABLED_ACCESSIBILITY_SERVICES_SEPARATOR);
136 private static final Set<ComponentName> sInstalledServices = new HashSet<ComponentName>();
138 private final Map<String, String> mLongPressTimeoutValuetoTitleMap =
139 new HashMap<String, String>();
141 private final Configuration mCurConfig = new Configuration();
143 private final PackageMonitor mSettingsPackageMonitor = new SettingsPackageMonitor();
145 private final Handler mHandler = new Handler() {
147 public void dispatchMessage(Message msg) {
148 super.dispatchMessage(msg);
149 loadInstalledServices();
150 updateServicesPreferences();
154 private final Uri mLockScreenRotationUri = Uri.withAppendedPath(Settings.System.CONTENT_URI,
155 Settings.System.ACCELEROMETER_ROTATION);
157 private final ContentObserver mSettingsContentObserver = new ContentObserver(mHandler) {
159 public void onChange(boolean selfChange, Uri uri) {
160 if (mLockScreenRotationUri.equals(uri)) {
162 final boolean lockRotationEnabled = (Settings.System.getInt(
163 getActivity().getContentResolver(),
164 Settings.System.ACCELEROMETER_ROTATION) == 0);
165 mToggleLockScreenRotationPreference.setChecked(lockRotationEnabled);
166 } catch (SettingNotFoundException e) {
173 // Preference controls.
174 private PreferenceCategory mServicesCategory;
175 private PreferenceCategory mSystemsCategory;
177 private CheckBoxPreference mToggleLargeTextPreference;
178 private CheckBoxPreference mTogglePowerButtonEndsCallPreference;
179 private CheckBoxPreference mToggleLockScreenRotationPreference;
180 private CheckBoxPreference mToggleSpeakPasswordPreference;
181 private ListPreference mSelectLongPressTimeoutPreference;
182 private AccessibilityEnableScriptInjectionPreference mToggleScriptInjectionPreference;
183 private Preference mNoServicesMessagePreference;
185 private int mLongPressTimeoutDefault;
188 public void onCreate(Bundle icicle) {
189 super.onCreate(icicle);
190 addPreferencesFromResource(R.xml.accessibility_settings);
191 initializeAllPreferences();
195 public void onResume() {
197 loadInstalledServices();
198 updateAllPreferences();
199 if (mServicesCategory.getPreference(0) == mNoServicesMessagePreference) {
200 offerInstallAccessibilitySerivceOnce();
202 mSettingsPackageMonitor.register(getActivity(), getActivity().getMainLooper(), false);
203 getActivity().getContentResolver().registerContentObserver(mLockScreenRotationUri, false,
204 mSettingsContentObserver);
208 public void onPause() {
209 mSettingsPackageMonitor.unregister();
210 getContentResolver().unregisterContentObserver(mSettingsContentObserver);
214 public boolean onPreferenceChange(Preference preference, Object newValue) {
215 if (preference == mSelectLongPressTimeoutPreference) {
216 String stringValue = (String) newValue;
217 Settings.Secure.putInt(getContentResolver(),
218 Settings.Secure.LONG_PRESS_TIMEOUT, Integer.parseInt(stringValue));
219 mSelectLongPressTimeoutPreference.setSummary(
220 mLongPressTimeoutValuetoTitleMap.get(stringValue));
227 public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) {
228 if (mToggleLargeTextPreference == preference) {
229 handleToggleLargeTextPreferenceClick();
231 } else if (mTogglePowerButtonEndsCallPreference == preference) {
232 handleTogglePowerButtonEndsCallPreferenceClick();
234 } else if (mToggleLockScreenRotationPreference == preference) {
235 handleLockScreenRotationPreferenceClick();
237 } else if (mToggleSpeakPasswordPreference == preference) {
238 handleToggleSpeakPasswordPreferenceClick();
240 return super.onPreferenceTreeClick(preferenceScreen, preference);
243 private void handleToggleLargeTextPreferenceClick() {
245 mCurConfig.fontScale = mToggleLargeTextPreference.isChecked() ? LARGE_FONT_SCALE : 1;
246 ActivityManagerNative.getDefault().updatePersistentConfiguration(mCurConfig);
247 } catch (RemoteException re) {
252 private void handleTogglePowerButtonEndsCallPreferenceClick() {
253 Settings.Secure.putInt(getContentResolver(),
254 Settings.Secure.INCALL_POWER_BUTTON_BEHAVIOR,
255 (mTogglePowerButtonEndsCallPreference.isChecked()
256 ? Settings.Secure.INCALL_POWER_BUTTON_BEHAVIOR_HANGUP
257 : Settings.Secure.INCALL_POWER_BUTTON_BEHAVIOR_SCREEN_OFF));
260 private void handleLockScreenRotationPreferenceClick() {
262 IWindowManager wm = IWindowManager.Stub.asInterface(
263 ServiceManager.getService(Context.WINDOW_SERVICE));
264 if (!mToggleLockScreenRotationPreference.isChecked()) {
267 wm.freezeRotation(Surface.ROTATION_0);
269 } catch (RemoteException exc) {
270 Log.w(TAG, "Unable to save auto-rotate setting");
274 private void handleToggleSpeakPasswordPreferenceClick() {
275 Settings.Secure.putInt(getContentResolver(),
276 Settings.Secure.ACCESSIBILITY_SPEAK_PASSWORD,
277 mToggleSpeakPasswordPreference.isChecked() ? 1 : 0);
280 private void initializeAllPreferences() {
281 mServicesCategory = (PreferenceCategory) findPreference(SERVICES_CATEGORY);
282 mSystemsCategory = (PreferenceCategory) findPreference(SYSTEM_CATEGORY);
285 mToggleLargeTextPreference =
286 (CheckBoxPreference) findPreference(TOGGLE_LARGE_TEXT_PREFERENCE);
288 // Power button ends calls.
289 mTogglePowerButtonEndsCallPreference =
290 (CheckBoxPreference) findPreference(TOGGLE_POWER_BUTTON_ENDS_CALL_PREFERENCE);
291 if (!KeyCharacterMap.deviceHasKey(KeyEvent.KEYCODE_POWER)
292 || !Utils.isVoiceCapable(getActivity())) {
293 mSystemsCategory.removePreference(mTogglePowerButtonEndsCallPreference);
296 // Lock screen rotation.
297 mToggleLockScreenRotationPreference =
298 (CheckBoxPreference) findPreference(TOGGLE_LOCK_SCREEN_ROTATION_PREFERENCE);
301 mToggleSpeakPasswordPreference =
302 (CheckBoxPreference) findPreference(TOGGLE_SPEAK_PASSWORD_PREFERENCE);
304 // Long press timeout.
305 mSelectLongPressTimeoutPreference =
306 (ListPreference) findPreference(SELECT_LONG_PRESS_TIMEOUT_PREFERENCE);
307 mSelectLongPressTimeoutPreference.setOnPreferenceChangeListener(this);
308 if (mLongPressTimeoutValuetoTitleMap.size() == 0) {
309 String[] timeoutValues = getResources().getStringArray(
310 R.array.long_press_timeout_selector_values);
311 mLongPressTimeoutDefault = Integer.parseInt(timeoutValues[0]);
312 String[] timeoutTitles = getResources().getStringArray(
313 R.array.long_press_timeout_selector_titles);
314 final int timeoutValueCount = timeoutValues.length;
315 for (int i = 0; i < timeoutValueCount; i++) {
316 mLongPressTimeoutValuetoTitleMap.put(timeoutValues[i], timeoutTitles[i]);
321 mToggleScriptInjectionPreference = (AccessibilityEnableScriptInjectionPreference)
322 findPreference(TOGGLE_SCRIPT_INJECTION_PREFERENCE);
325 private void updateAllPreferences() {
326 updateServicesPreferences();
327 updateSystemPreferences();
330 private void updateServicesPreferences() {
331 // Since services category is auto generated we have to do a pass
332 // to generate it since services can come and go and then based on
333 // the global accessibility state to decided whether it is enabled.
336 mServicesCategory.removeAll();
338 AccessibilityManager accessibilityManager = AccessibilityManager.getInstance(getActivity());
340 List<AccessibilityServiceInfo> installedServices =
341 accessibilityManager.getInstalledAccessibilityServiceList();
342 Set<ComponentName> enabledServices = getEnabledServicesFromSettings(getActivity());
344 final boolean accessibilityEnabled = Settings.Secure.getInt(getContentResolver(),
345 Settings.Secure.ACCESSIBILITY_ENABLED, 0) == 1;
347 for (int i = 0, count = installedServices.size(); i < count; ++i) {
348 AccessibilityServiceInfo info = installedServices.get(i);
350 PreferenceScreen preference = getPreferenceManager().createPreferenceScreen(
352 String title = info.getResolveInfo().loadLabel(getPackageManager()).toString();
354 ServiceInfo serviceInfo = info.getResolveInfo().serviceInfo;
355 ComponentName componentName = new ComponentName(serviceInfo.packageName,
358 preference.setKey(componentName.flattenToString());
360 preference.setTitle(title);
361 final boolean serviceEnabled = accessibilityEnabled
362 && enabledServices.contains(componentName);
363 if (serviceEnabled) {
364 preference.setSummary(getString(R.string.accessibility_service_state_on));
366 preference.setSummary(getString(R.string.accessibility_service_state_off));
369 preference.setOrder(i);
370 preference.setFragment(ToggleAccessibilityServicePreferenceFragment.class.getName());
371 preference.setPersistent(true);
373 Bundle extras = preference.getExtras();
374 extras.putString(EXTRA_PREFERENCE_KEY, preference.getKey());
375 extras.putBoolean(EXTRA_CHECKED, serviceEnabled);
376 extras.putString(EXTRA_TITLE, title);
378 String description = info.loadDescription(getPackageManager());
379 if (TextUtils.isEmpty(description)) {
380 description = getString(R.string.accessibility_service_default_description);
382 extras.putString(EXTRA_SUMMARY, description);
384 CharSequence applicationLabel = info.getResolveInfo().loadLabel(getPackageManager());
386 extras.putString(EXTRA_ENABLE_WARNING_TITLE, getString(
387 R.string.accessibility_service_security_warning_title, applicationLabel));
388 extras.putString(EXTRA_ENABLE_WARNING_MESSAGE, getString(
389 R.string.accessibility_service_security_warning_summary, applicationLabel));
391 extras.putString(EXTRA_DISABLE_WARNING_TITLE, getString(
392 R.string.accessibility_service_disable_warning_title,
394 extras.putString(EXTRA_DISABLE_WARNING_MESSAGE, getString(
395 R.string.accessibility_service_disable_warning_summary,
398 String settingsClassName = info.getSettingsActivityName();
399 if (!TextUtils.isEmpty(settingsClassName)) {
400 extras.putString(EXTRA_SETTINGS_TITLE,
401 getString(R.string.accessibility_menu_item_settings));
402 extras.putString(EXTRA_SETTINGS_COMPONENT_NAME,
403 new ComponentName(info.getResolveInfo().serviceInfo.packageName,
404 settingsClassName).flattenToString());
407 mServicesCategory.addPreference(preference);
410 if (mServicesCategory.getPreferenceCount() == 0) {
411 if (mNoServicesMessagePreference == null) {
412 mNoServicesMessagePreference = new Preference(getActivity()) {
414 protected void onBindView(View view) {
415 super.onBindView(view);
417 LinearLayout containerView =
418 (LinearLayout) view.findViewById(R.id.message_container);
419 containerView.setGravity(Gravity.CENTER);
421 TextView summaryView = (TextView) view.findViewById(R.id.summary);
422 String title = getString(R.string.accessibility_no_services_installed);
423 summaryView.setText(title);
426 mNoServicesMessagePreference.setPersistent(false);
427 mNoServicesMessagePreference.setLayoutResource(
428 R.layout.text_description_preference);
429 mNoServicesMessagePreference.setSelectable(false);
431 mServicesCategory.addPreference(mNoServicesMessagePreference);
435 private void updateSystemPreferences() {
438 mCurConfig.updateFrom(ActivityManagerNative.getDefault().getConfiguration());
439 } catch (RemoteException re) {
442 mToggleLargeTextPreference.setChecked(mCurConfig.fontScale == LARGE_FONT_SCALE);
444 // Power button ends calls.
445 if (KeyCharacterMap.deviceHasKey(KeyEvent.KEYCODE_POWER)
446 && Utils.isVoiceCapable(getActivity())) {
447 final int incallPowerBehavior = Settings.Secure.getInt(getContentResolver(),
448 Settings.Secure.INCALL_POWER_BUTTON_BEHAVIOR,
449 Settings.Secure.INCALL_POWER_BUTTON_BEHAVIOR_DEFAULT);
450 final boolean powerButtonEndsCall =
451 (incallPowerBehavior == Settings.Secure.INCALL_POWER_BUTTON_BEHAVIOR_HANGUP);
452 mTogglePowerButtonEndsCallPreference.setChecked(powerButtonEndsCall);
455 // Auto-rotate screen
456 final boolean lockRotationEnabled = Settings.System.getInt(getContentResolver(),
457 Settings.System.ACCELEROMETER_ROTATION, 0) != 1;
458 mToggleLockScreenRotationPreference.setChecked(lockRotationEnabled);
461 final boolean speakPasswordEnabled = Settings.Secure.getInt(getContentResolver(),
462 Settings.Secure.ACCESSIBILITY_SPEAK_PASSWORD, 0) != 0;
463 mToggleSpeakPasswordPreference.setChecked(speakPasswordEnabled);
465 // Long press timeout.
466 final int longPressTimeout = Settings.Secure.getInt(getContentResolver(),
467 Settings.Secure.LONG_PRESS_TIMEOUT, mLongPressTimeoutDefault);
468 String value = String.valueOf(longPressTimeout);
469 mSelectLongPressTimeoutPreference.setValue(value);
470 mSelectLongPressTimeoutPreference.setSummary(mLongPressTimeoutValuetoTitleMap.get(value));
473 final boolean scriptInjectionAllowed = (Settings.Secure.getInt(getContentResolver(),
474 Settings.Secure.ACCESSIBILITY_SCRIPT_INJECTION, 0) == 1);
475 mToggleScriptInjectionPreference.setInjectionAllowed(scriptInjectionAllowed);
478 private void offerInstallAccessibilitySerivceOnce() {
479 // There is always one preference - if no services it is just a message.
480 if (mServicesCategory.getPreference(0) != mNoServicesMessagePreference) {
483 SharedPreferences preferences = getActivity().getPreferences(Context.MODE_PRIVATE);
484 final boolean offerInstallService = !preferences.getBoolean(
485 KEY_INSTALL_ACCESSIBILITY_SERVICE_OFFERED_ONCE, false);
486 if (offerInstallService) {
487 preferences.edit().putBoolean(KEY_INSTALL_ACCESSIBILITY_SERVICE_OFFERED_ONCE,
489 // Notify user that they do not have any accessibility
490 // services installed and direct them to Market to get TalkBack.
491 showDialog(DIALOG_ID_NO_ACCESSIBILITY_SERVICES);
496 public Dialog onCreateDialog(int dialogId) {
498 case DIALOG_ID_NO_ACCESSIBILITY_SERVICES:
499 return new AlertDialog.Builder(getActivity())
500 .setTitle(R.string.accessibility_service_no_apps_title)
501 .setMessage(R.string.accessibility_service_no_apps_message)
502 .setPositiveButton(android.R.string.ok,
503 new DialogInterface.OnClickListener() {
504 public void onClick(DialogInterface dialog, int which) {
505 // dismiss the dialog before launching the activity otherwise
506 // the dialog removal occurs after onSaveInstanceState which
507 // triggers an exception
508 removeDialog(DIALOG_ID_NO_ACCESSIBILITY_SERVICES);
509 String screenreaderMarketLink = SystemProperties.get(
510 SYSTEM_PROPERTY_MARKET_URL,
511 DEFAULT_SCREENREADER_MARKET_LINK);
512 Uri marketUri = Uri.parse(screenreaderMarketLink);
513 Intent marketIntent = new Intent(Intent.ACTION_VIEW, marketUri);
514 startActivity(marketIntent);
517 .setNegativeButton(android.R.string.cancel, null)
524 private void loadInstalledServices() {
525 List<AccessibilityServiceInfo> installedServiceInfos =
526 AccessibilityManager.getInstance(getActivity())
527 .getInstalledAccessibilityServiceList();
528 Set<ComponentName> installedServices = sInstalledServices;
529 installedServices.clear();
530 final int installedServiceInfoCount = installedServiceInfos.size();
531 for (int i = 0; i < installedServiceInfoCount; i++) {
532 ResolveInfo resolveInfo = installedServiceInfos.get(i).getResolveInfo();
533 ComponentName installedService = new ComponentName(
534 resolveInfo.serviceInfo.packageName,
535 resolveInfo.serviceInfo.name);
536 installedServices.add(installedService);
540 private static Set<ComponentName> getEnabledServicesFromSettings(Context context) {
541 String enabledServicesSetting = Settings.Secure.getString(context.getContentResolver(),
542 Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES);
543 if (enabledServicesSetting == null) {
544 enabledServicesSetting = "";
546 Set<ComponentName> enabledServices = new HashSet<ComponentName>();
547 SimpleStringSplitter colonSplitter = sStringColonSplitter;
548 colonSplitter.setString(enabledServicesSetting);
549 while (colonSplitter.hasNext()) {
550 String componentNameString = colonSplitter.next();
551 ComponentName enabledService = ComponentName.unflattenFromString(
552 componentNameString);
553 if (enabledService != null) {
554 enabledServices.add(enabledService);
557 return enabledServices;
560 private class SettingsPackageMonitor extends PackageMonitor {
563 public void onPackageAdded(String packageName, int uid) {
564 Message message = mHandler.obtainMessage();
565 mHandler.sendMessageDelayed(message, DELAY_UPDATE_SERVICES_MILLIS);
569 public void onPackageAppeared(String packageName, int reason) {
570 Message message = mHandler.obtainMessage();
571 mHandler.sendMessageDelayed(message, DELAY_UPDATE_SERVICES_MILLIS);
575 public void onPackageDisappeared(String packageName, int reason) {
576 Message message = mHandler.obtainMessage();
577 mHandler.sendMessageDelayed(message, DELAY_UPDATE_SERVICES_MILLIS);
581 public void onPackageRemoved(String packageName, int uid) {
582 Message message = mHandler.obtainMessage();
583 mHandler.sendMessageDelayed(message, DELAY_UPDATE_SERVICES_MILLIS);
587 private static ToggleSwitch createAndAddActionBarToggleSwitch(Activity activity) {
588 ToggleSwitch toggleSwitch = new ToggleSwitch(activity);
589 final int padding = activity.getResources().getDimensionPixelSize(
590 R.dimen.action_bar_switch_padding);
591 toggleSwitch.setPadding(0, 0, padding, 0);
592 activity.getActionBar().setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM,
593 ActionBar.DISPLAY_SHOW_CUSTOM);
594 activity.getActionBar().setCustomView(toggleSwitch,
595 new ActionBar.LayoutParams(ActionBar.LayoutParams.WRAP_CONTENT,
596 ActionBar.LayoutParams.WRAP_CONTENT,
597 Gravity.CENTER_VERTICAL | Gravity.RIGHT));
601 public static class ToggleSwitch extends Switch {
603 private OnBeforeCheckedChangeListener mOnBeforeListener;
605 public static interface OnBeforeCheckedChangeListener {
606 public boolean onBeforeCheckedChanged(ToggleSwitch toggleSwitch, boolean checked);
609 public ToggleSwitch(Context context) {
613 public void setOnBeforeCheckedChangeListener(OnBeforeCheckedChangeListener listener) {
614 mOnBeforeListener = listener;
618 public void setChecked(boolean checked) {
619 if (mOnBeforeListener != null
620 && mOnBeforeListener.onBeforeCheckedChanged(this, checked)) {
623 super.setChecked(checked);
626 public void setCheckedInternal(boolean checked) {
627 super.setChecked(checked);
631 public static class ToggleAccessibilityServicePreferenceFragment
632 extends SettingsPreferenceFragment implements DialogInterface.OnClickListener {
634 private static final int DIALOG_ID_ENABLE_WARNING = 1;
635 private static final int DIALOG_ID_DISABLE_WARNING = 2;
637 private String mPreferenceKey;
639 private ToggleSwitch mToggleSwitch;
641 private CharSequence mEnableWarningTitle;
642 private CharSequence mEnableWarningMessage;
643 private CharSequence mDisableWarningTitle;
644 private CharSequence mDisableWarningMessage;
646 private Preference mSummaryPreference;
648 private CharSequence mSettingsTitle;
649 private Intent mSettingsIntent;
651 private int mShownDialogId;
653 // TODO: Showing sub-sub fragment does not handle the activity title
654 // so we do it but this is wrong. Do a real fix when there is time.
655 private CharSequence mOldActivityTitle;
658 public void onCreate(Bundle savedInstanceState) {
659 super.onCreate(savedInstanceState);
660 PreferenceScreen preferenceScreen = getPreferenceManager().createPreferenceScreen(
662 setPreferenceScreen(preferenceScreen);
663 mSummaryPreference = new Preference(getActivity()) {
665 protected void onBindView(View view) {
666 super.onBindView(view);
667 TextView summaryView = (TextView) view.findViewById(R.id.summary);
668 summaryView.setText(getSummary());
669 sendAccessibilityEvent(summaryView);
672 private void sendAccessibilityEvent(View view) {
673 // Since the view is still not attached we create, populate,
674 // and send the event directly since we do not know when it
675 // will be attached and posting commands is not as clean.
676 AccessibilityManager accessibilityManager =
677 AccessibilityManager.getInstance(getActivity());
678 if (accessibilityManager.isEnabled()) {
679 AccessibilityEvent event = AccessibilityEvent.obtain();
680 event.setEventType(AccessibilityEvent.TYPE_VIEW_FOCUSED);
681 view.onInitializeAccessibilityEvent(event);
682 view.dispatchPopulateAccessibilityEvent(event);
683 accessibilityManager.sendAccessibilityEvent(event);
687 mSummaryPreference.setPersistent(false);
688 mSummaryPreference.setLayoutResource(R.layout.text_description_preference);
689 preferenceScreen.addPreference(mSummaryPreference);
693 public void onViewCreated(View view, Bundle savedInstanceState) {
694 super.onViewCreated(view, savedInstanceState);
695 installActionBarToggleSwitch();
697 getListView().setDivider(null);
698 getListView().setEnabled(false);
702 public void onDestroyView() {
703 getActivity().getActionBar().setCustomView(null);
704 if (mOldActivityTitle != null) {
705 getActivity().getActionBar().setTitle(mOldActivityTitle);
707 mToggleSwitch.setOnBeforeCheckedChangeListener(null);
708 super.onDestroyView();
711 public void onPreferenceToggled(String preferenceKey, boolean enabled) {
712 // Parse the enabled services.
713 Set<ComponentName> enabledServices = getEnabledServicesFromSettings(getActivity());
715 // Determine enabled services and accessibility state.
716 ComponentName toggledService = ComponentName.unflattenFromString(preferenceKey);
717 final boolean accessibilityEnabled;
719 // Enabling at least one service enables accessibility.
720 accessibilityEnabled = true;
721 enabledServices.add(toggledService);
723 // Check how many enabled and installed services are present.
724 int enabledAndInstalledServiceCount = 0;
725 Set<ComponentName> installedServices = sInstalledServices;
726 for (ComponentName enabledService : enabledServices) {
727 if (installedServices.contains(enabledService)) {
728 enabledAndInstalledServiceCount++;
731 // Disabling the last service disables accessibility.
732 accessibilityEnabled = enabledAndInstalledServiceCount > 1
733 || (enabledAndInstalledServiceCount == 1
734 && !installedServices.contains(toggledService));
735 enabledServices.remove(toggledService);
738 // Update the enabled services setting.
739 StringBuilder enabledServicesBuilder = new StringBuilder();
740 // Keep the enabled services even if they are not installed since we have
741 // no way to know whether the application restore process has completed.
742 // In general the system should be responsible for the clean up not settings.
743 for (ComponentName enabledService : enabledServices) {
744 enabledServicesBuilder.append(enabledService.flattenToString());
745 enabledServicesBuilder.append(ENABLED_ACCESSIBILITY_SERVICES_SEPARATOR);
747 final int enabledServicesBuilderLength = enabledServicesBuilder.length();
748 if (enabledServicesBuilderLength > 0) {
749 enabledServicesBuilder.deleteCharAt(enabledServicesBuilderLength - 1);
751 Settings.Secure.putString(getContentResolver(),
752 Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
753 enabledServicesBuilder.toString());
755 // Update accessibility enabled.
756 Settings.Secure.putInt(getContentResolver(),
757 Settings.Secure.ACCESSIBILITY_ENABLED, accessibilityEnabled ? 1 : 0);
761 public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
762 super.onCreateOptionsMenu(menu, inflater);
763 MenuItem menuItem = menu.add(mSettingsTitle);
764 menuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
765 menuItem.setIntent(mSettingsIntent);
769 public Dialog onCreateDialog(int dialogId) {
770 CharSequence title = null;
771 CharSequence message = null;
773 case DIALOG_ID_ENABLE_WARNING:
774 mShownDialogId = DIALOG_ID_ENABLE_WARNING;
775 title = mEnableWarningTitle;
776 message = mEnableWarningMessage;
778 case DIALOG_ID_DISABLE_WARNING:
779 mShownDialogId = DIALOG_ID_DISABLE_WARNING;
780 title = mDisableWarningTitle;
781 message = mDisableWarningMessage;
784 throw new IllegalArgumentException();
786 return new AlertDialog.Builder(getActivity())
788 .setIcon(android.R.drawable.ic_dialog_alert)
791 .setPositiveButton(android.R.string.ok, this)
792 .setNegativeButton(android.R.string.cancel, this)
797 public void onClick(DialogInterface dialog, int which) {
798 final boolean checked;
800 case DialogInterface.BUTTON_POSITIVE:
801 checked = (mShownDialogId == DIALOG_ID_ENABLE_WARNING);
802 mToggleSwitch.setCheckedInternal(checked);
803 getArguments().putBoolean(EXTRA_CHECKED, checked);
804 onPreferenceToggled(mPreferenceKey, checked);
806 case DialogInterface.BUTTON_NEGATIVE:
807 checked = (mShownDialogId == DIALOG_ID_DISABLE_WARNING);
808 mToggleSwitch.setCheckedInternal(checked);
809 getArguments().putBoolean(EXTRA_CHECKED, checked);
810 onPreferenceToggled(mPreferenceKey, checked);
813 throw new IllegalArgumentException();
817 private void installActionBarToggleSwitch() {
818 mToggleSwitch = createAndAddActionBarToggleSwitch(getActivity());
819 mToggleSwitch.setOnBeforeCheckedChangeListener(new OnBeforeCheckedChangeListener() {
821 public boolean onBeforeCheckedChanged(ToggleSwitch toggleSwitch, boolean checked) {
823 if (!TextUtils.isEmpty(mEnableWarningMessage)) {
824 toggleSwitch.setCheckedInternal(false);
825 getArguments().putBoolean(EXTRA_CHECKED, false);
826 showDialog(DIALOG_ID_ENABLE_WARNING);
829 onPreferenceToggled(mPreferenceKey, true);
831 if (!TextUtils.isEmpty(mDisableWarningMessage)) {
832 toggleSwitch.setCheckedInternal(true);
833 getArguments().putBoolean(EXTRA_CHECKED, true);
834 showDialog(DIALOG_ID_DISABLE_WARNING);
837 onPreferenceToggled(mPreferenceKey, false);
844 private void processArguments() {
845 Bundle arguments = getArguments();
848 mPreferenceKey = arguments.getString(EXTRA_PREFERENCE_KEY);
851 final boolean enabled = arguments.getBoolean(EXTRA_CHECKED);
852 mToggleSwitch.setCheckedInternal(enabled);
855 PreferenceActivity activity = (PreferenceActivity) getActivity();
856 if (!activity.onIsMultiPane() || activity.onIsHidingHeaders()) {
857 mOldActivityTitle = getActivity().getTitle();
858 String title = arguments.getString(EXTRA_TITLE);
859 getActivity().getActionBar().setTitle(title);
863 String summary = arguments.getString(EXTRA_SUMMARY);
864 mSummaryPreference.setSummary(summary);
866 // Settings title and intent.
867 String settingsTitle = arguments.getString(EXTRA_SETTINGS_TITLE);
868 String settingsComponentName = arguments.getString(EXTRA_SETTINGS_COMPONENT_NAME);
869 if (!TextUtils.isEmpty(settingsTitle) && !TextUtils.isEmpty(settingsComponentName)) {
870 Intent settingsIntent = new Intent(Intent.ACTION_MAIN).setComponent(
871 ComponentName.unflattenFromString(settingsComponentName.toString()));
872 if (!getPackageManager().queryIntentActivities(settingsIntent, 0).isEmpty()) {
873 mSettingsTitle = settingsTitle;
874 mSettingsIntent = settingsIntent;
875 setHasOptionsMenu(true);
879 // Enable warning title.
880 mEnableWarningTitle = arguments.getCharSequence(
881 AccessibilitySettings.EXTRA_ENABLE_WARNING_TITLE);
883 // Enable warning message.
884 mEnableWarningMessage = arguments.getCharSequence(
885 AccessibilitySettings.EXTRA_ENABLE_WARNING_MESSAGE);
887 // Disable warning title.
888 mDisableWarningTitle = arguments.getString(
889 AccessibilitySettings.EXTRA_DISABLE_WARNING_TITLE);
891 // Disable warning message.
892 mDisableWarningMessage = arguments.getString(
893 AccessibilitySettings.EXTRA_DISABLE_WARNING_MESSAGE);