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_INSTALL_ACCESSIBILITY_SERVICE_OFFERED_ONCE =
96 "key_install_accessibility_service_offered_once";
98 // Preference categories
99 private static final String SERVICES_CATEGORY = "services_category";
100 private static final String SYSTEM_CATEGORY = "system_category";
103 private static final String TOGGLE_LARGE_TEXT_PREFERENCE = "toggle_large_text_preference";
104 private static final String TOGGLE_POWER_BUTTON_ENDS_CALL_PREFERENCE =
105 "toggle_power_button_ends_call_preference";
106 private static final String TOGGLE_LOCK_SCREEN_ROTATION_PREFERENCE =
107 "toggle_lock_screen_rotation_preference";
108 private static final String TOGGLE_SPEAK_PASSWORD_PREFERENCE =
109 "toggle_speak_password_preference";
110 private static final String SELECT_LONG_PRESS_TIMEOUT_PREFERENCE =
111 "select_long_press_timeout_preference";
112 private static final String TOGGLE_SCRIPT_INJECTION_PREFERENCE =
113 "toggle_script_injection_preference";
115 // Extras passed to sub-fragments.
116 private static final String EXTRA_PREFERENCE_KEY = "preference_key";
117 private static final String EXTRA_CHECKED = "checked";
118 private static final String EXTRA_TITLE = "title";
119 private static final String EXTRA_SUMMARY = "summary";
120 private static final String EXTRA_ENABLE_WARNING_TITLE = "enable_warning_title";
121 private static final String EXTRA_ENABLE_WARNING_MESSAGE = "enable_warning_message";
122 private static final String EXTRA_DISABLE_WARNING_TITLE = "disable_warning_title";
123 private static final String EXTRA_DISABLE_WARNING_MESSAGE = "disable_warning_message";
124 private static final String EXTRA_SETTINGS_TITLE = "settings_title";
125 private static final String EXTRA_SETTINGS_COMPONENT_NAME = "settings_component_name";
128 private static final int DIALOG_ID_NO_ACCESSIBILITY_SERVICES = 1;
130 // Auxiliary members.
131 private final static SimpleStringSplitter sStringColonSplitter =
132 new SimpleStringSplitter(ENABLED_ACCESSIBILITY_SERVICES_SEPARATOR);
134 private static final Set<ComponentName> sInstalledServices = new HashSet<ComponentName>();
136 private final Map<String, String> mLongPressTimeoutValuetoTitleMap =
137 new HashMap<String, String>();
139 private final Configuration mCurConfig = new Configuration();
141 private final PackageMonitor mSettingsPackageMonitor = new SettingsPackageMonitor();
143 private final Handler mHandler = new Handler() {
145 public void dispatchMessage(Message msg) {
146 super.dispatchMessage(msg);
147 loadInstalledServices();
148 updateServicesPreferences();
152 // Preference controls.
153 private PreferenceCategory mServicesCategory;
154 private PreferenceCategory mSystemsCategory;
156 private CheckBoxPreference mToggleLargeTextPreference;
157 private CheckBoxPreference mTogglePowerButtonEndsCallPreference;
158 private CheckBoxPreference mToggleLockScreenRotationPreference;
159 private CheckBoxPreference mToggleSpeakPasswordPreference;
160 private ListPreference mSelectLongPressTimeoutPreference;
161 private AccessibilityEnableScriptInjectionPreference mToggleScriptInjectionPreference;
162 private Preference mNoServicesMessagePreference;
164 private int mLongPressTimeoutDefault;
167 public void onCreate(Bundle icicle) {
168 super.onCreate(icicle);
169 addPreferencesFromResource(R.xml.accessibility_settings);
170 initializeAllPreferences();
174 public void onResume() {
176 loadInstalledServices();
177 updateAllPreferences();
178 if (mServicesCategory.getPreference(0) == mNoServicesMessagePreference) {
179 offerInstallAccessibilitySerivceOnce();
181 mSettingsPackageMonitor.register(getActivity(), getActivity().getMainLooper(), false);
185 public void onPause() {
186 mSettingsPackageMonitor.unregister();
190 public boolean onPreferenceChange(Preference preference, Object newValue) {
191 if (preference == mSelectLongPressTimeoutPreference) {
192 String stringValue = (String) newValue;
193 Settings.Secure.putInt(getContentResolver(),
194 Settings.Secure.LONG_PRESS_TIMEOUT, Integer.parseInt(stringValue));
195 mSelectLongPressTimeoutPreference.setSummary(
196 mLongPressTimeoutValuetoTitleMap.get(stringValue));
203 public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) {
204 if (mToggleLargeTextPreference == preference) {
205 handleToggleLargeTextPreferenceClick();
207 } else if (mTogglePowerButtonEndsCallPreference == preference) {
208 handleTogglePowerButtonEndsCallPreferenceClick();
210 } else if (mToggleLockScreenRotationPreference == preference) {
211 handleLockScreenRotationPreferenceClick();
213 } else if (mToggleSpeakPasswordPreference == preference) {
214 handleToggleSpeakPasswordPreferenceClick();
216 return super.onPreferenceTreeClick(preferenceScreen, preference);
219 private void handleToggleLargeTextPreferenceClick() {
221 mCurConfig.fontScale = mToggleLargeTextPreference.isChecked() ? LARGE_FONT_SCALE : 1;
222 ActivityManagerNative.getDefault().updatePersistentConfiguration(mCurConfig);
223 } catch (RemoteException re) {
228 private void handleTogglePowerButtonEndsCallPreferenceClick() {
229 Settings.Secure.putInt(getContentResolver(),
230 Settings.Secure.INCALL_POWER_BUTTON_BEHAVIOR,
231 (mTogglePowerButtonEndsCallPreference.isChecked()
232 ? Settings.Secure.INCALL_POWER_BUTTON_BEHAVIOR_HANGUP
233 : Settings.Secure.INCALL_POWER_BUTTON_BEHAVIOR_SCREEN_OFF));
236 private void handleLockScreenRotationPreferenceClick() {
238 IWindowManager wm = IWindowManager.Stub.asInterface(
239 ServiceManager.getService(Context.WINDOW_SERVICE));
240 if (!mToggleLockScreenRotationPreference.isChecked()) {
243 wm.freezeRotation(Surface.ROTATION_0);
245 } catch (RemoteException exc) {
246 Log.w(TAG, "Unable to save auto-rotate setting");
250 private void handleToggleSpeakPasswordPreferenceClick() {
251 Settings.Secure.putInt(getContentResolver(),
252 Settings.Secure.ACCESSIBILITY_SPEAK_PASSWORD,
253 mToggleSpeakPasswordPreference.isChecked() ? 1 : 0);
256 private void initializeAllPreferences() {
257 mServicesCategory = (PreferenceCategory) findPreference(SERVICES_CATEGORY);
258 mSystemsCategory = (PreferenceCategory) findPreference(SYSTEM_CATEGORY);
261 mToggleLargeTextPreference =
262 (CheckBoxPreference) findPreference(TOGGLE_LARGE_TEXT_PREFERENCE);
264 // Power button ends calls.
265 mTogglePowerButtonEndsCallPreference =
266 (CheckBoxPreference) findPreference(TOGGLE_POWER_BUTTON_ENDS_CALL_PREFERENCE);
267 if (!KeyCharacterMap.deviceHasKey(KeyEvent.KEYCODE_POWER)
268 || !Utils.isVoiceCapable(getActivity())) {
269 mSystemsCategory.removePreference(mTogglePowerButtonEndsCallPreference);
272 // Lock screen rotation.
273 mToggleLockScreenRotationPreference =
274 (CheckBoxPreference) findPreference(TOGGLE_LOCK_SCREEN_ROTATION_PREFERENCE);
277 mToggleSpeakPasswordPreference =
278 (CheckBoxPreference) findPreference(TOGGLE_SPEAK_PASSWORD_PREFERENCE);
280 // Long press timeout.
281 mSelectLongPressTimeoutPreference =
282 (ListPreference) findPreference(SELECT_LONG_PRESS_TIMEOUT_PREFERENCE);
283 mSelectLongPressTimeoutPreference.setOnPreferenceChangeListener(this);
284 if (mLongPressTimeoutValuetoTitleMap.size() == 0) {
285 String[] timeoutValues = getResources().getStringArray(
286 R.array.long_press_timeout_selector_values);
287 mLongPressTimeoutDefault = Integer.parseInt(timeoutValues[0]);
288 String[] timeoutTitles = getResources().getStringArray(
289 R.array.long_press_timeout_selector_titles);
290 final int timeoutValueCount = timeoutValues.length;
291 for (int i = 0; i < timeoutValueCount; i++) {
292 mLongPressTimeoutValuetoTitleMap.put(timeoutValues[i], timeoutTitles[i]);
297 mToggleScriptInjectionPreference = (AccessibilityEnableScriptInjectionPreference)
298 findPreference(TOGGLE_SCRIPT_INJECTION_PREFERENCE);
301 private void updateAllPreferences() {
302 updateServicesPreferences();
303 updateSystemPreferences();
306 private void updateServicesPreferences() {
307 // Since services category is auto generated we have to do a pass
308 // to generate it since services can come and go and then based on
309 // the global accessibility state to decided whether it is enabled.
312 mServicesCategory.removeAll();
314 AccessibilityManager accessibilityManager = AccessibilityManager.getInstance(getActivity());
316 List<AccessibilityServiceInfo> installedServices =
317 accessibilityManager.getInstalledAccessibilityServiceList();
318 Set<ComponentName> enabledServices = getEnabledServicesFromSettings(getActivity());
320 final boolean accessibilityEnabled = Settings.Secure.getInt(getContentResolver(),
321 Settings.Secure.ACCESSIBILITY_ENABLED, 0) == 1;
323 for (int i = 0, count = installedServices.size(); i < count; ++i) {
324 AccessibilityServiceInfo info = installedServices.get(i);
326 PreferenceScreen preference = getPreferenceManager().createPreferenceScreen(
328 String title = info.getResolveInfo().loadLabel(getPackageManager()).toString();
330 ServiceInfo serviceInfo = info.getResolveInfo().serviceInfo;
331 ComponentName componentName = new ComponentName(serviceInfo.packageName,
334 preference.setKey(componentName.flattenToString());
336 preference.setTitle(title);
337 final boolean serviceEnabled = accessibilityEnabled
338 && enabledServices.contains(componentName);
339 if (serviceEnabled) {
340 preference.setSummary(getString(R.string.accessibility_service_state_on));
342 preference.setSummary(getString(R.string.accessibility_service_state_off));
345 preference.setOrder(i);
346 preference.setFragment(ToggleAccessibilityServicePreferenceFragment.class.getName());
347 preference.setPersistent(true);
349 Bundle extras = preference.getExtras();
350 extras.putString(EXTRA_PREFERENCE_KEY, preference.getKey());
351 extras.putBoolean(EXTRA_CHECKED, serviceEnabled);
352 extras.putString(EXTRA_TITLE, title);
354 String description = info.loadDescription(getPackageManager());
355 if (TextUtils.isEmpty(description)) {
356 description = getString(R.string.accessibility_service_default_description);
358 extras.putString(EXTRA_SUMMARY, description);
360 CharSequence applicationLabel = info.getResolveInfo().loadLabel(getPackageManager());
362 extras.putString(EXTRA_ENABLE_WARNING_TITLE, getString(
363 R.string.accessibility_service_security_warning_title, applicationLabel));
364 extras.putString(EXTRA_ENABLE_WARNING_MESSAGE, getString(
365 R.string.accessibility_service_security_warning_summary, applicationLabel));
367 extras.putString(EXTRA_DISABLE_WARNING_TITLE, getString(
368 R.string.accessibility_service_disable_warning_title,
370 extras.putString(EXTRA_DISABLE_WARNING_MESSAGE, getString(
371 R.string.accessibility_service_disable_warning_summary,
374 String settingsClassName = info.getSettingsActivityName();
375 if (!TextUtils.isEmpty(settingsClassName)) {
376 extras.putString(EXTRA_SETTINGS_TITLE,
377 getString(R.string.accessibility_menu_item_settings));
378 extras.putString(EXTRA_SETTINGS_COMPONENT_NAME,
379 new ComponentName(info.getResolveInfo().serviceInfo.packageName,
380 settingsClassName).flattenToString());
383 mServicesCategory.addPreference(preference);
386 if (mServicesCategory.getPreferenceCount() == 0) {
387 if (mNoServicesMessagePreference == null) {
388 mNoServicesMessagePreference = new Preference(getActivity()) {
390 protected void onBindView(View view) {
391 super.onBindView(view);
393 LinearLayout containerView =
394 (LinearLayout) view.findViewById(R.id.message_container);
395 containerView.setGravity(Gravity.CENTER);
397 TextView summaryView = (TextView) view.findViewById(R.id.summary);
398 String title = getString(R.string.accessibility_no_services_installed);
399 summaryView.setText(title);
402 mNoServicesMessagePreference.setPersistent(false);
403 mNoServicesMessagePreference.setLayoutResource(
404 R.layout.text_description_preference);
405 mNoServicesMessagePreference.setSelectable(false);
407 mServicesCategory.addPreference(mNoServicesMessagePreference);
411 private void updateSystemPreferences() {
414 mCurConfig.updateFrom(ActivityManagerNative.getDefault().getConfiguration());
415 } catch (RemoteException re) {
418 mToggleLargeTextPreference.setChecked(mCurConfig.fontScale == LARGE_FONT_SCALE);
420 // Power button ends calls.
421 if (KeyCharacterMap.deviceHasKey(KeyEvent.KEYCODE_POWER)
422 && Utils.isVoiceCapable(getActivity())) {
423 final int incallPowerBehavior = Settings.Secure.getInt(getContentResolver(),
424 Settings.Secure.INCALL_POWER_BUTTON_BEHAVIOR,
425 Settings.Secure.INCALL_POWER_BUTTON_BEHAVIOR_DEFAULT);
426 final boolean powerButtonEndsCall =
427 (incallPowerBehavior == Settings.Secure.INCALL_POWER_BUTTON_BEHAVIOR_HANGUP);
428 mTogglePowerButtonEndsCallPreference.setChecked(powerButtonEndsCall);
431 // Auto-rotate screen
432 final boolean lockRotationEnabled = Settings.System.getInt(getContentResolver(),
433 Settings.System.ACCELEROMETER_ROTATION, 0) != 1;
434 mToggleLockScreenRotationPreference.setChecked(lockRotationEnabled);
437 final boolean speakPasswordEnabled = Settings.Secure.getInt(getContentResolver(),
438 Settings.Secure.ACCESSIBILITY_SPEAK_PASSWORD, 0) != 0;
439 mToggleSpeakPasswordPreference.setChecked(speakPasswordEnabled);
441 // Long press timeout.
442 final int longPressTimeout = Settings.Secure.getInt(getContentResolver(),
443 Settings.Secure.LONG_PRESS_TIMEOUT, mLongPressTimeoutDefault);
444 String value = String.valueOf(longPressTimeout);
445 mSelectLongPressTimeoutPreference.setValue(value);
446 mSelectLongPressTimeoutPreference.setSummary(mLongPressTimeoutValuetoTitleMap.get(value));
449 final boolean scriptInjectionAllowed = (Settings.Secure.getInt(getContentResolver(),
450 Settings.Secure.ACCESSIBILITY_SCRIPT_INJECTION, 0) == 1);
451 mToggleScriptInjectionPreference.setInjectionAllowed(scriptInjectionAllowed);
454 private void offerInstallAccessibilitySerivceOnce() {
455 // There is always one preference - if no services it is just a message.
456 if (mServicesCategory.getPreference(0) != mNoServicesMessagePreference) {
459 SharedPreferences preferences = getActivity().getPreferences(Context.MODE_PRIVATE);
460 final boolean offerInstallService = !preferences.getBoolean(
461 KEY_INSTALL_ACCESSIBILITY_SERVICE_OFFERED_ONCE, false);
462 if (offerInstallService) {
463 preferences.edit().putBoolean(KEY_INSTALL_ACCESSIBILITY_SERVICE_OFFERED_ONCE,
465 // Notify user that they do not have any accessibility
466 // services installed and direct them to Market to get TalkBack.
467 showDialog(DIALOG_ID_NO_ACCESSIBILITY_SERVICES);
472 public Dialog onCreateDialog(int dialogId) {
474 case DIALOG_ID_NO_ACCESSIBILITY_SERVICES:
475 return new AlertDialog.Builder(getActivity())
476 .setTitle(R.string.accessibility_service_no_apps_title)
477 .setMessage(R.string.accessibility_service_no_apps_message)
478 .setPositiveButton(android.R.string.ok,
479 new DialogInterface.OnClickListener() {
480 public void onClick(DialogInterface dialog, int which) {
481 // dismiss the dialog before launching the activity otherwise
482 // the dialog removal occurs after onSaveInstanceState which
483 // triggers an exception
484 removeDialog(DIALOG_ID_NO_ACCESSIBILITY_SERVICES);
485 String screenreaderMarketLink = SystemProperties.get(
486 SYSTEM_PROPERTY_MARKET_URL,
487 DEFAULT_SCREENREADER_MARKET_LINK);
488 Uri marketUri = Uri.parse(screenreaderMarketLink);
489 Intent marketIntent = new Intent(Intent.ACTION_VIEW, marketUri);
490 startActivity(marketIntent);
493 .setNegativeButton(android.R.string.cancel, null)
500 private void loadInstalledServices() {
501 List<AccessibilityServiceInfo> installedServiceInfos =
502 AccessibilityManager.getInstance(getActivity())
503 .getInstalledAccessibilityServiceList();
504 Set<ComponentName> installedServices = sInstalledServices;
505 installedServices.clear();
506 final int installedServiceInfoCount = installedServiceInfos.size();
507 for (int i = 0; i < installedServiceInfoCount; i++) {
508 ResolveInfo resolveInfo = installedServiceInfos.get(i).getResolveInfo();
509 ComponentName installedService = new ComponentName(
510 resolveInfo.serviceInfo.packageName,
511 resolveInfo.serviceInfo.name);
512 installedServices.add(installedService);
516 private static Set<ComponentName> getEnabledServicesFromSettings(Context context) {
517 String enabledServicesSetting = Settings.Secure.getString(context.getContentResolver(),
518 Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES);
519 if (enabledServicesSetting == null) {
520 enabledServicesSetting = "";
522 Set<ComponentName> enabledServices = new HashSet<ComponentName>();
523 SimpleStringSplitter colonSplitter = sStringColonSplitter;
524 colonSplitter.setString(enabledServicesSetting);
525 while (colonSplitter.hasNext()) {
526 String componentNameString = colonSplitter.next();
527 ComponentName enabledService = ComponentName.unflattenFromString(
528 componentNameString);
529 if (enabledService != null) {
530 enabledServices.add(enabledService);
533 return enabledServices;
536 private class SettingsPackageMonitor extends PackageMonitor {
539 public void onPackageAdded(String packageName, int uid) {
540 Message message = mHandler.obtainMessage();
541 mHandler.sendMessageDelayed(message, DELAY_UPDATE_SERVICES_MILLIS);
545 public void onPackageAppeared(String packageName, int reason) {
546 Message message = mHandler.obtainMessage();
547 mHandler.sendMessageDelayed(message, DELAY_UPDATE_SERVICES_MILLIS);
551 public void onPackageDisappeared(String packageName, int reason) {
552 Message message = mHandler.obtainMessage();
553 mHandler.sendMessageDelayed(message, DELAY_UPDATE_SERVICES_MILLIS);
557 public void onPackageRemoved(String packageName, int uid) {
558 Message message = mHandler.obtainMessage();
559 mHandler.sendMessageDelayed(message, DELAY_UPDATE_SERVICES_MILLIS);
563 private static ToggleSwitch createAndAddActionBarToggleSwitch(Activity activity) {
564 ToggleSwitch toggleSwitch = new ToggleSwitch(activity);
565 final int padding = activity.getResources().getDimensionPixelSize(
566 R.dimen.action_bar_switch_padding);
567 toggleSwitch.setPadding(0, 0, padding, 0);
568 activity.getActionBar().setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM,
569 ActionBar.DISPLAY_SHOW_CUSTOM);
570 activity.getActionBar().setCustomView(toggleSwitch,
571 new ActionBar.LayoutParams(ActionBar.LayoutParams.WRAP_CONTENT,
572 ActionBar.LayoutParams.WRAP_CONTENT,
573 Gravity.CENTER_VERTICAL | Gravity.RIGHT));
577 public static class ToggleSwitch extends Switch {
579 private OnBeforeCheckedChangeListener mOnBeforeListener;
581 public static interface OnBeforeCheckedChangeListener {
582 public boolean onBeforeCheckedChanged(ToggleSwitch toggleSwitch, boolean checked);
585 public ToggleSwitch(Context context) {
589 public void setOnBeforeCheckedChangeListener(OnBeforeCheckedChangeListener listener) {
590 mOnBeforeListener = listener;
594 public void setChecked(boolean checked) {
595 if (mOnBeforeListener != null
596 && mOnBeforeListener.onBeforeCheckedChanged(this, checked)) {
599 super.setChecked(checked);
602 public void setCheckedInternal(boolean checked) {
603 super.setChecked(checked);
607 public static class ToggleAccessibilityServicePreferenceFragment
608 extends SettingsPreferenceFragment implements DialogInterface.OnClickListener {
610 private static final int DIALOG_ID_ENABLE_WARNING = 1;
611 private static final int DIALOG_ID_DISABLE_WARNING = 2;
613 private String mPreferenceKey;
615 private ToggleSwitch mToggleSwitch;
617 private CharSequence mEnableWarningTitle;
618 private CharSequence mEnableWarningMessage;
619 private CharSequence mDisableWarningTitle;
620 private CharSequence mDisableWarningMessage;
622 private Preference mSummaryPreference;
624 private CharSequence mSettingsTitle;
625 private Intent mSettingsIntent;
627 private int mShownDialogId;
629 // TODO: Showing sub-sub fragment does not handle the activity title
630 // so we do it but this is wrong. Do a real fix when there is time.
631 private CharSequence mOldActivityTitle;
634 public void onCreate(Bundle savedInstanceState) {
635 super.onCreate(savedInstanceState);
636 PreferenceScreen preferenceScreen = getPreferenceManager().createPreferenceScreen(
638 setPreferenceScreen(preferenceScreen);
639 mSummaryPreference = new Preference(getActivity()) {
641 protected void onBindView(View view) {
642 super.onBindView(view);
643 TextView summaryView = (TextView) view.findViewById(R.id.summary);
644 summaryView.setText(getSummary());
645 sendAccessibilityEvent(summaryView);
648 private void sendAccessibilityEvent(View view) {
649 // Since the view is still not attached we create, populate,
650 // and send the event directly since we do not know when it
651 // will be attached and posting commands is not as clean.
652 AccessibilityManager accessibilityManager =
653 AccessibilityManager.getInstance(getActivity());
654 if (accessibilityManager.isEnabled()) {
655 AccessibilityEvent event = AccessibilityEvent.obtain();
656 event.setEventType(AccessibilityEvent.TYPE_VIEW_FOCUSED);
657 view.onInitializeAccessibilityEvent(event);
658 view.dispatchPopulateAccessibilityEvent(event);
659 accessibilityManager.sendAccessibilityEvent(event);
663 mSummaryPreference.setPersistent(false);
664 mSummaryPreference.setLayoutResource(R.layout.text_description_preference);
665 preferenceScreen.addPreference(mSummaryPreference);
669 public void onViewCreated(View view, Bundle savedInstanceState) {
670 super.onViewCreated(view, savedInstanceState);
671 installActionBarToggleSwitch();
673 getListView().setDivider(null);
674 getListView().setEnabled(false);
678 public void onDestroyView() {
679 getActivity().getActionBar().setCustomView(null);
680 if (mOldActivityTitle != null) {
681 getActivity().getActionBar().setTitle(mOldActivityTitle);
683 mToggleSwitch.setOnBeforeCheckedChangeListener(null);
684 super.onDestroyView();
687 public void onPreferenceToggled(String preferenceKey, boolean enabled) {
688 // Parse the enabled services.
689 Set<ComponentName> enabledServices = getEnabledServicesFromSettings(getActivity());
691 // Determine enabled services and accessibility state.
692 ComponentName toggledService = ComponentName.unflattenFromString(preferenceKey);
693 final boolean accessibilityEnabled;
695 // Enabling at least one service enables accessibility.
696 accessibilityEnabled = true;
697 enabledServices.add(toggledService);
699 // Check how many enabled and installed services are present.
700 int enabledAndInstalledServiceCount = 0;
701 Set<ComponentName> installedServices = sInstalledServices;
702 for (ComponentName enabledService : enabledServices) {
703 if (installedServices.contains(enabledService)) {
704 enabledAndInstalledServiceCount++;
707 // Disabling the last service disables accessibility.
708 accessibilityEnabled = enabledAndInstalledServiceCount > 1
709 || (enabledAndInstalledServiceCount == 1
710 && !installedServices.contains(toggledService));
711 enabledServices.remove(toggledService);
714 // Update the enabled services setting.
715 StringBuilder enabledServicesBuilder = new StringBuilder();
716 // Keep the enabled services even if they are not installed since we have
717 // no way to know whether the application restore process has completed.
718 // In general the system should be responsible for the clean up not settings.
719 for (ComponentName enabledService : enabledServices) {
720 enabledServicesBuilder.append(enabledService.flattenToString());
721 enabledServicesBuilder.append(ENABLED_ACCESSIBILITY_SERVICES_SEPARATOR);
723 final int enabledServicesBuilderLength = enabledServicesBuilder.length();
724 if (enabledServicesBuilderLength > 0) {
725 enabledServicesBuilder.deleteCharAt(enabledServicesBuilderLength - 1);
727 Settings.Secure.putString(getContentResolver(),
728 Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
729 enabledServicesBuilder.toString());
731 // Update accessibility enabled.
732 Settings.Secure.putInt(getContentResolver(),
733 Settings.Secure.ACCESSIBILITY_ENABLED, accessibilityEnabled ? 1 : 0);
737 public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
738 super.onCreateOptionsMenu(menu, inflater);
739 MenuItem menuItem = menu.add(mSettingsTitle);
740 menuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
741 menuItem.setIntent(mSettingsIntent);
745 public Dialog onCreateDialog(int dialogId) {
746 CharSequence title = null;
747 CharSequence message = null;
749 case DIALOG_ID_ENABLE_WARNING:
750 mShownDialogId = DIALOG_ID_ENABLE_WARNING;
751 title = mEnableWarningTitle;
752 message = mEnableWarningMessage;
754 case DIALOG_ID_DISABLE_WARNING:
755 mShownDialogId = DIALOG_ID_DISABLE_WARNING;
756 title = mDisableWarningTitle;
757 message = mDisableWarningMessage;
760 throw new IllegalArgumentException();
762 return new AlertDialog.Builder(getActivity())
764 .setIcon(android.R.drawable.ic_dialog_alert)
767 .setPositiveButton(android.R.string.ok, this)
768 .setNegativeButton(android.R.string.cancel, this)
773 public void onClick(DialogInterface dialog, int which) {
774 final boolean checked;
776 case DialogInterface.BUTTON_POSITIVE:
777 checked = (mShownDialogId == DIALOG_ID_ENABLE_WARNING);
778 mToggleSwitch.setCheckedInternal(checked);
779 getArguments().putBoolean(EXTRA_CHECKED, checked);
780 onPreferenceToggled(mPreferenceKey, checked);
782 case DialogInterface.BUTTON_NEGATIVE:
783 checked = (mShownDialogId == DIALOG_ID_DISABLE_WARNING);
784 mToggleSwitch.setCheckedInternal(checked);
785 getArguments().putBoolean(EXTRA_CHECKED, checked);
786 onPreferenceToggled(mPreferenceKey, checked);
789 throw new IllegalArgumentException();
793 private void installActionBarToggleSwitch() {
794 mToggleSwitch = createAndAddActionBarToggleSwitch(getActivity());
795 mToggleSwitch.setOnBeforeCheckedChangeListener(new OnBeforeCheckedChangeListener() {
797 public boolean onBeforeCheckedChanged(ToggleSwitch toggleSwitch, boolean checked) {
799 if (!TextUtils.isEmpty(mEnableWarningMessage)) {
800 toggleSwitch.setCheckedInternal(false);
801 getArguments().putBoolean(EXTRA_CHECKED, false);
802 showDialog(DIALOG_ID_ENABLE_WARNING);
805 onPreferenceToggled(mPreferenceKey, true);
807 if (!TextUtils.isEmpty(mDisableWarningMessage)) {
808 toggleSwitch.setCheckedInternal(true);
809 getArguments().putBoolean(EXTRA_CHECKED, true);
810 showDialog(DIALOG_ID_DISABLE_WARNING);
813 onPreferenceToggled(mPreferenceKey, false);
820 private void processArguments() {
821 Bundle arguments = getArguments();
824 mPreferenceKey = arguments.getString(EXTRA_PREFERENCE_KEY);
827 final boolean enabled = arguments.getBoolean(EXTRA_CHECKED);
828 mToggleSwitch.setCheckedInternal(enabled);
831 PreferenceActivity activity = (PreferenceActivity) getActivity();
832 if (!activity.onIsMultiPane() || activity.onIsHidingHeaders()) {
833 mOldActivityTitle = getActivity().getTitle();
834 String title = arguments.getString(EXTRA_TITLE);
835 getActivity().getActionBar().setTitle(title);
839 String summary = arguments.getString(EXTRA_SUMMARY);
840 mSummaryPreference.setSummary(summary);
842 // Settings title and intent.
843 String settingsTitle = arguments.getString(EXTRA_SETTINGS_TITLE);
844 String settingsComponentName = arguments.getString(EXTRA_SETTINGS_COMPONENT_NAME);
845 if (!TextUtils.isEmpty(settingsTitle) && !TextUtils.isEmpty(settingsComponentName)) {
846 Intent settingsIntent = new Intent(Intent.ACTION_MAIN).setComponent(
847 ComponentName.unflattenFromString(settingsComponentName.toString()));
848 if (!getPackageManager().queryIntentActivities(settingsIntent, 0).isEmpty()) {
849 mSettingsTitle = settingsTitle;
850 mSettingsIntent = settingsIntent;
851 setHasOptionsMenu(true);
855 // Enable warning title.
856 mEnableWarningTitle = arguments.getCharSequence(
857 AccessibilitySettings.EXTRA_ENABLE_WARNING_TITLE);
859 // Enable warning message.
860 mEnableWarningMessage = arguments.getCharSequence(
861 AccessibilitySettings.EXTRA_ENABLE_WARNING_MESSAGE);
863 // Disable warning title.
864 mDisableWarningTitle = arguments.getString(
865 AccessibilitySettings.EXTRA_DISABLE_WARNING_TITLE);
867 // Disable warning message.
868 mDisableWarningMessage = arguments.getString(
869 AccessibilitySettings.EXTRA_DISABLE_WARNING_MESSAGE);