OSDN Git Service

Adding a system preference whether to speak passwords in accessibility mode (settings).
[android-x86/packages-apps-Settings.git] / src / com / android / settings / AccessibilitySettings.java
1 /*
2  * Copyright (C) 2009 The Android Open Source Project
3  *
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
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  */
16
17 package com.android.settings;
18
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;
64
65 import com.android.internal.content.PackageMonitor;
66 import com.android.settings.AccessibilitySettings.ToggleSwitch.OnBeforeCheckedChangeListener;
67
68 import java.util.HashMap;
69 import java.util.HashSet;
70 import java.util.List;
71 import java.util.Map;
72 import java.util.Set;
73
74 /**
75  * Activity with the accessibility settings.
76  */
77 public class AccessibilitySettings extends SettingsPreferenceFragment implements DialogCreatable,
78         Preference.OnPreferenceChangeListener {
79     private static final String TAG = "AccessibilitySettings";
80
81     private static final String DEFAULT_SCREENREADER_MARKET_LINK =
82         "market://search?q=pname:com.google.android.marvin.talkback";
83
84     private static final float LARGE_FONT_SCALE = 1.3f;
85
86     private static final String SYSTEM_PROPERTY_MARKET_URL = "ro.screenreader.market";
87
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;
92
93     private static final char ENABLED_ACCESSIBILITY_SERVICES_SEPARATOR = ':';
94
95     private static final String KEY_ACCESSIBILITY_TUTORIAL_LAUNCHED_ONCE =
96         "key_accessibility_tutorial_launched_once";
97
98     private static final String KEY_INSTALL_ACCESSIBILITY_SERVICE_OFFERED_ONCE =
99         "key_install_accessibility_service_offered_once";
100
101     // Preference categories
102     private static final String SERVICES_CATEGORY = "services_category";
103     private static final String SYSTEM_CATEGORY = "system_category";
104
105     // Preferences
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";
119
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";
131
132     // Dialog IDs.
133     private static final int DIALOG_ID_NO_ACCESSIBILITY_SERVICES = 1;
134
135     // Auxiliary members.
136     private final static SimpleStringSplitter sStringColonSplitter =
137         new SimpleStringSplitter(ENABLED_ACCESSIBILITY_SERVICES_SEPARATOR);
138
139     private static final Set<ComponentName> sInstalledServices = new HashSet<ComponentName>();
140
141     private final Map<String, String> mLongPressTimeoutValuetoTitleMap =
142         new HashMap<String, String>();
143
144     private final Configuration mCurConfig = new Configuration();
145
146     private final PackageMonitor mSettingsPackageMonitor = new SettingsPackageMonitor();
147
148     private final Handler mHandler = new Handler() {
149         @Override
150         public void dispatchMessage(Message msg) {
151             super.dispatchMessage(msg);
152             loadInstalledServices();
153             updateServicesPreferences();
154         }
155     };
156
157     // Preference controls.
158     private PreferenceCategory mServicesCategory;
159     private PreferenceCategory mSystemsCategory;
160
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;
169
170     private int mLongPressTimeoutDefault;
171
172     @Override
173     public void onCreate(Bundle icicle) {
174         super.onCreate(icicle);
175         addPreferencesFromResource(R.xml.accessibility_settings);
176         initializeAllPreferences();
177     }
178
179     @Override
180     public void onResume() {
181         super.onResume();
182         loadInstalledServices();
183         updateAllPreferences();
184         if (mServicesCategory.getPreference(0) == mNoServicesMessagePreference) {
185             offerInstallAccessibilitySerivceOnce();
186         }
187         mSettingsPackageMonitor.register(getActivity(), false);
188     }
189
190     @Override
191     public void onPause() {
192         mSettingsPackageMonitor.unregister();
193         super.onPause();
194     }
195
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));
203             return true;
204         }
205         return false;
206     }
207
208     @Override
209     public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) {
210         if (mToggleLargeTextPreference == preference) {
211             handleToggleLargeTextPreferenceClick();
212             return true;
213         } else if (mTogglePowerButtonEndsCallPreference == preference) {
214             handleTogglePowerButtonEndsCallPreferenceClick();
215             return true;
216         } else if (mToggleAutoRotateScreenPreference == preference) {
217             handleToggleAutoRotateScreenPreferenceClick();
218             return true;
219         } else if (mToggleSpeakPasswordPreference == preference) {
220             handleToggleSpeakPasswordPreferenceClick();
221         }
222         return super.onPreferenceTreeClick(preferenceScreen, preference);
223     }
224
225     private void handleToggleLargeTextPreferenceClick() {
226         try {
227             mCurConfig.fontScale = mToggleLargeTextPreference.isChecked() ? LARGE_FONT_SCALE : 1;
228             ActivityManagerNative.getDefault().updatePersistentConfiguration(mCurConfig);
229         } catch (RemoteException re) {
230             /* ignore */
231         }
232     }
233
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));
240     }
241
242     private void handleToggleAutoRotateScreenPreferenceClick() {
243         try {
244             IWindowManager wm = IWindowManager.Stub.asInterface(
245                     ServiceManager.getService(Context.WINDOW_SERVICE));
246             if (mToggleAutoRotateScreenPreference.isChecked()) {
247                 wm.thawRotation();
248             } else {
249                 wm.freezeRotation(Surface.ROTATION_0);
250             }
251         } catch (RemoteException exc) {
252             Log.w(TAG, "Unable to save auto-rotate setting");
253         }
254     }
255
256     private void handleToggleSpeakPasswordPreferenceClick() {
257         Settings.Secure.putInt(getContentResolver(),
258                 Settings.Secure.ACCESSIBILITY_SPEAK_PASSWORD,
259                 mToggleSpeakPasswordPreference.isChecked() ? 1 : 0);
260     }
261
262     private void initializeAllPreferences() {
263         mServicesCategory = (PreferenceCategory) findPreference(SERVICES_CATEGORY);
264         mSystemsCategory = (PreferenceCategory) findPreference(SYSTEM_CATEGORY);
265
266         // Large text.
267         mToggleLargeTextPreference =
268             (CheckBoxPreference) findPreference(TOGGLE_LARGE_TEXT_PREFERENCE);
269
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);
276         }
277
278         // Auto-rotate screen
279         mToggleAutoRotateScreenPreference =
280             (CheckBoxPreference) findPreference(TOGGLE_AUTO_ROTATE_SCREEN_PREFERENCE);
281
282         // Speak passwords.
283         mToggleSpeakPasswordPreference =
284             (CheckBoxPreference) findPreference(TOGGLE_SPEAK_PASSWORD_PREFERENCE);
285
286         // Touch exploration enabled.
287         mToggleTouchExplorationPreference = findPreference(TOGGLE_TOUCH_EXPLORATION_PREFERENCE);
288
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]);
302             }
303         }
304
305         // Script injection.
306         mToggleScriptInjectionPreference = (AccessibilityEnableScriptInjectionPreference)
307             findPreference(TOGGLE_SCRIPT_INJECTION_PREFERENCE);
308     }
309
310     private void updateAllPreferences() {
311         updateServicesPreferences();
312         updateSystemPreferences();
313     }
314
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.
319
320         // Generate.
321         mServicesCategory.removeAll();
322
323         AccessibilityManager accessibilityManager = AccessibilityManager.getInstance(getActivity());
324
325         List<AccessibilityServiceInfo> installedServices =
326             accessibilityManager.getInstalledAccessibilityServiceList();
327         Set<ComponentName> enabledServices = getEnabledServicesFromSettings(getActivity());
328
329         final boolean accessibilityEnabled = Settings.Secure.getInt(getContentResolver(),
330                 Settings.Secure.ACCESSIBILITY_ENABLED, 0) == 1;
331
332         for (int i = 0, count = installedServices.size(); i < count; ++i) {
333             AccessibilityServiceInfo info = installedServices.get(i);
334
335             PreferenceScreen preference = getPreferenceManager().createPreferenceScreen(
336                     getActivity());
337             String title = info.getResolveInfo().loadLabel(getPackageManager()).toString();
338
339             ServiceInfo serviceInfo = info.getResolveInfo().serviceInfo;
340             ComponentName componentName = new ComponentName(serviceInfo.packageName,
341                     serviceInfo.name);
342
343             preference.setKey(componentName.flattenToString());
344
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));
350             } else {
351                 preference.setSummary(getString(R.string.accessibility_service_state_off));
352             }
353
354             preference.setOrder(i);
355             preference.setFragment(ToggleAccessibilityServiceFragment.class.getName());
356             preference.setPersistent(true);
357
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);
362
363             String description = info.getDescription();
364             if (TextUtils.isEmpty(description)) {
365                 description = getString(R.string.accessibility_service_default_description);
366             }
367             extras.putString(EXTRA_SUMMARY, description);
368
369             CharSequence applicationLabel = info.getResolveInfo().loadLabel(getPackageManager());
370
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));
375
376             extras.putString(EXTRA_DISABLE_WARNING_TITLE, getString(
377                     R.string.accessibility_service_disable_warning_title,
378                     applicationLabel));
379             extras.putString(EXTRA_DISABLE_WARNING_MESSAGE, getString(
380                     R.string.accessibility_service_disable_warning_summary,
381                     applicationLabel));
382
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());
390             }
391
392             mServicesCategory.addPreference(preference);
393         }
394
395         if (mServicesCategory.getPreferenceCount() == 0) {
396             if (mNoServicesMessagePreference == null) {
397                 mNoServicesMessagePreference = new Preference(getActivity()) {
398                     @Override
399                     protected void onBindView(View view) {
400                         super.onBindView(view);
401
402                         LinearLayout containerView =
403                             (LinearLayout) view.findViewById(R.id.message_container);
404                         containerView.setGravity(Gravity.CENTER);
405
406                         TextView summaryView = (TextView) view.findViewById(R.id.summary);
407                         String title = getString(R.string.accessibility_no_services_installed);
408                         summaryView.setText(title);
409                     }
410                 };
411                 mNoServicesMessagePreference.setPersistent(false);
412                 mNoServicesMessagePreference.setLayoutResource(
413                         R.layout.text_description_preference);
414                 mNoServicesMessagePreference.setSelectable(false);
415             }
416             mServicesCategory.addPreference(mNoServicesMessagePreference);
417         }
418     }
419
420     private void updateSystemPreferences() {
421         // Large text.
422         try {
423             mCurConfig.updateFrom(ActivityManagerNative.getDefault().getConfiguration());
424         } catch (RemoteException re) {
425             /* ignore */
426         }
427         mToggleLargeTextPreference.setChecked(mCurConfig.fontScale == LARGE_FONT_SCALE);
428
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);
438         }
439
440         // Auto-rotate screen
441         final boolean autoRotationEnabled = Settings.System.getInt(getContentResolver(),
442                 Settings.System.ACCELEROMETER_ROTATION, 0) != 0;
443         mToggleAutoRotateScreenPreference.setChecked(autoRotationEnabled);
444
445         // Speak passwords.
446         final boolean speakPasswordEnabled = Settings.Secure.getInt(getContentResolver(),
447                 Settings.Secure.ACCESSIBILITY_SPEAK_PASSWORD, 0) != 0;
448         mToggleSpeakPasswordPreference.setChecked(speakPasswordEnabled);
449
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);
459             } else {
460                 mToggleTouchExplorationPreference.setSummary(
461                         getString(R.string.accessibility_service_state_off));
462                 mToggleTouchExplorationPreference.getExtras().putBoolean(EXTRA_CHECKED, false);
463             }
464
465         } else {
466             mSystemsCategory.removePreference(mToggleTouchExplorationPreference);
467         }
468
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));
475
476         // Script injection.
477         final boolean  scriptInjectionAllowed = (Settings.Secure.getInt(getContentResolver(),
478                 Settings.Secure.ACCESSIBILITY_SCRIPT_INJECTION, 0) == 1);
479         mToggleScriptInjectionPreference.setInjectionAllowed(scriptInjectionAllowed);
480     }
481
482     private void offerInstallAccessibilitySerivceOnce() {
483         // There is always one preference - if no services it is just a message.
484         if (mServicesCategory.getPreference(0) != mNoServicesMessagePreference) {
485             return;
486         }
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,
492                     true).commit();
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);
496         }
497     }
498
499     @Override
500     public Dialog onCreateDialog(int dialogId) {
501         switch (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);
519                             }
520                     })
521                     .setNegativeButton(android.R.string.cancel, null)
522                     .create();
523             default:
524                 return null;
525         }
526     }
527
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);
541         }
542     }
543
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 = "";
549         }
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);
559             }
560         }
561         return enabledServices;
562     }
563
564     private class SettingsPackageMonitor extends PackageMonitor {
565
566         @Override
567         public void onPackageAdded(String packageName, int uid) {
568             Message message = mHandler.obtainMessage();
569             mHandler.sendMessageDelayed(message, DELAY_UPDATE_SERVICES_MILLIS);
570         }
571
572         @Override
573         public void onPackageAppeared(String packageName, int reason) {
574             Message message = mHandler.obtainMessage();
575             mHandler.sendMessageDelayed(message, DELAY_UPDATE_SERVICES_MILLIS);
576         }
577
578         @Override
579         public void onPackageDisappeared(String packageName, int reason) {
580             Message message = mHandler.obtainMessage();
581             mHandler.sendMessageDelayed(message, DELAY_UPDATE_SERVICES_MILLIS);
582         }
583
584         @Override
585         public void onPackageRemoved(String packageName, int uid) {
586             Message message = mHandler.obtainMessage();
587             mHandler.sendMessageDelayed(message, DELAY_UPDATE_SERVICES_MILLIS);
588         }
589     }
590
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));
602         return toggleSwitch;
603     }
604
605     public static class ToggleSwitch extends Switch {
606
607         private OnBeforeCheckedChangeListener mOnBeforeListener;
608
609         public static interface OnBeforeCheckedChangeListener {
610             public boolean onBeforeCheckedChanged(ToggleSwitch toggleSwitch, boolean checked);
611         }
612
613         public ToggleSwitch(Context context) {
614             super(context);
615         }
616
617         public void setOnBeforeCheckedChangeListener(OnBeforeCheckedChangeListener listener) {
618             mOnBeforeListener = listener;
619         }
620
621         @Override
622         public void setChecked(boolean checked) {
623             if (mOnBeforeListener != null
624                     && mOnBeforeListener.onBeforeCheckedChanged(this, checked)) {
625                 return;
626             }
627             super.setChecked(checked);
628         }
629
630         public void setCheckedInternal(boolean checked) {
631             super.setChecked(checked);
632         }
633     }
634
635     public static class ToggleAccessibilityServiceFragment extends TogglePreferenceFragment {
636         @Override
637         public void onPreferenceToggled(String preferenceKey, boolean enabled) {
638             // Parse the enabled services.
639             Set<ComponentName> enabledServices = getEnabledServicesFromSettings(getActivity());
640
641             // Determine enabled services and accessibility state.
642             ComponentName toggledService = ComponentName.unflattenFromString(preferenceKey);
643             final boolean accessibilityEnabled;
644             if (enabled) {
645                 // Enabling at least one service enables accessibility.
646                 accessibilityEnabled = true;
647                 enabledServices.add(toggledService);
648             } else {
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++;
655                     }
656                 }
657                 // Disabling the last service disables accessibility.
658                 accessibilityEnabled = enabledAndInstalledServiceCount > 1
659                     || (enabledAndInstalledServiceCount == 1
660                             && !installedServices.contains(toggledService));
661                 enabledServices.remove(toggledService);
662             }
663
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);
672             }
673             final int enabledServicesBuilderLength = enabledServicesBuilder.length();
674             if (enabledServicesBuilderLength > 0) {
675                 enabledServicesBuilder.deleteCharAt(enabledServicesBuilderLength - 1);
676             }
677             Settings.Secure.putString(getContentResolver(),
678                     Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
679                     enabledServicesBuilder.toString());
680
681             // Update accessibility enabled.
682             Settings.Secure.putInt(getContentResolver(),
683                     Settings.Secure.ACCESSIBILITY_ENABLED, accessibilityEnabled ? 1 : 0);
684         }
685     }
686
687     public static class ToggleTouchExplorationFragment extends TogglePreferenceFragment {
688         @Override
689         public void onPreferenceToggled(String preferenceKey, boolean enabled) {
690             Settings.Secure.putInt(getContentResolver(),
691                     Settings.Secure.TOUCH_EXPLORATION_ENABLED, enabled ? 1 : 0);
692             if (enabled) {
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,
698                             true).commit();
699                     Intent intent = new Intent(AccessibilityTutorialActivity.ACTION);
700                     getActivity().startActivity(intent);
701                 }
702             }
703         }
704     }
705
706     private abstract static class TogglePreferenceFragment extends SettingsPreferenceFragment
707             implements DialogInterface.OnClickListener {
708
709         private static final int DIALOG_ID_ENABLE_WARNING = 1;
710         private static final int DIALOG_ID_DISABLE_WARNING = 2;
711
712         private String mPreferenceKey;
713
714         private ToggleSwitch mToggleSwitch;
715
716         private CharSequence mEnableWarningTitle;
717         private CharSequence mEnableWarningMessage;
718         private CharSequence mDisableWarningTitle;
719         private CharSequence mDisableWarningMessage;
720         private Preference mSummaryPreference;
721
722         private CharSequence mSettingsTitle;
723         private Intent mSettingsIntent;
724
725         private int mShownDialogId;
726
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;
730
731         @Override
732         public void onCreate(Bundle savedInstanceState) {
733             super.onCreate(savedInstanceState);
734             PreferenceScreen preferenceScreen = getPreferenceManager().createPreferenceScreen(
735                     getActivity());
736             setPreferenceScreen(preferenceScreen);
737             mSummaryPreference = new Preference(getActivity()) {
738                 @Override
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);
744                 }
745
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);
758                     }
759                 }
760             };
761             mSummaryPreference.setPersistent(false);
762             mSummaryPreference.setLayoutResource(R.layout.text_description_preference);
763             preferenceScreen.addPreference(mSummaryPreference);
764         }
765
766         @Override
767         public void onViewCreated(View view, Bundle savedInstanceState) {
768             super.onViewCreated(view, savedInstanceState);
769             installActionBarToggleSwitch();
770             processArguments();
771             getListView().setDivider(null);
772             getListView().setEnabled(false);
773         }
774
775         @Override
776         public void onDestroyView() {
777             getActivity().getActionBar().setCustomView(null);
778             if (mOldActivityTitle != null) {
779                 getActivity().getActionBar().setTitle(mOldActivityTitle);
780             }
781             mToggleSwitch.setOnBeforeCheckedChangeListener(null);
782             super.onDestroyView();
783         }
784
785         public abstract void onPreferenceToggled(String preferenceKey, boolean value);
786
787         @Override
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);
793         }
794
795         @Override
796         public Dialog onCreateDialog(int dialogId) {
797             CharSequence title = null;
798             CharSequence message = null;
799             switch (dialogId) {
800                 case DIALOG_ID_ENABLE_WARNING:
801                     mShownDialogId = DIALOG_ID_ENABLE_WARNING;
802                     title = mEnableWarningTitle;
803                     message = mEnableWarningMessage;
804                     break;
805                 case DIALOG_ID_DISABLE_WARNING:
806                     mShownDialogId = DIALOG_ID_DISABLE_WARNING;
807                     title = mDisableWarningTitle;
808                     message = mDisableWarningMessage;
809                     break;
810                 default:
811                     throw new IllegalArgumentException();
812             }
813             return new AlertDialog.Builder(getActivity())
814                 .setTitle(title)
815                 .setIcon(android.R.drawable.ic_dialog_alert)
816                 .setMessage(message)
817                 .setCancelable(true)
818                 .setPositiveButton(android.R.string.ok, this)
819                 .setNegativeButton(android.R.string.cancel, this)
820                 .create();
821         }
822
823         @Override
824         public void onClick(DialogInterface dialog, int which) {
825             final boolean checked;
826             switch (which) {
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);
832                     break;
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);
838                     break;
839                 default:
840                     throw new IllegalArgumentException();
841             }
842         }
843
844         private void installActionBarToggleSwitch() {
845             mToggleSwitch = createAndAddActionBarToggleSwitch(getActivity());
846             mToggleSwitch.setOnBeforeCheckedChangeListener(new OnBeforeCheckedChangeListener() {
847                 @Override
848                 public boolean onBeforeCheckedChanged(ToggleSwitch toggleSwitch, boolean checked) {
849                     if (checked) {
850                         if (!TextUtils.isEmpty(mEnableWarningMessage)) {
851                             toggleSwitch.setCheckedInternal(false);
852                             getArguments().putBoolean(EXTRA_CHECKED, false);
853                             showDialog(DIALOG_ID_ENABLE_WARNING);
854                             return true;
855                         }
856                         onPreferenceToggled(mPreferenceKey, true);
857                     } else {
858                         if (!TextUtils.isEmpty(mDisableWarningMessage)) {
859                             toggleSwitch.setCheckedInternal(true);
860                             getArguments().putBoolean(EXTRA_CHECKED, true);
861                             showDialog(DIALOG_ID_DISABLE_WARNING);
862                             return true;
863                         }
864                         onPreferenceToggled(mPreferenceKey, false);
865                     }
866                     return false;
867                 }
868             });
869         }
870
871         private void processArguments() {
872             Bundle arguments = getArguments();
873
874             // Key.
875             mPreferenceKey = arguments.getString(EXTRA_PREFERENCE_KEY);
876
877             // Enabled.
878             final boolean enabled = arguments.getBoolean(EXTRA_CHECKED);
879             mToggleSwitch.setCheckedInternal(enabled);
880
881             // Title.
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);
887             }
888
889             // Summary.
890             String summary = arguments.getString(EXTRA_SUMMARY);
891             mSummaryPreference.setSummary(summary);
892
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);
903                 }
904             }
905
906             // Enable warning title.
907             mEnableWarningTitle = arguments.getCharSequence(
908                     AccessibilitySettings.EXTRA_ENABLE_WARNING_TITLE);
909
910             // Enable warning message.
911             mEnableWarningMessage = arguments.getCharSequence(
912                     AccessibilitySettings.EXTRA_ENABLE_WARNING_MESSAGE);
913
914             // Disable warning title.
915             mDisableWarningTitle = arguments.getString(
916                     AccessibilitySettings.EXTRA_DISABLE_WARNING_TITLE);
917
918             // Disable warning message.
919             mDisableWarningMessage = arguments.getString(
920                     AccessibilitySettings.EXTRA_DISABLE_WARNING_MESSAGE);
921         }
922     }
923 }