OSDN Git Service

Lock screen rotation accessibility preference not updated.
[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.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;
66
67 import com.android.internal.content.PackageMonitor;
68 import com.android.settings.AccessibilitySettings.ToggleSwitch.OnBeforeCheckedChangeListener;
69
70 import java.util.HashMap;
71 import java.util.HashSet;
72 import java.util.List;
73 import java.util.Map;
74 import java.util.Set;
75
76 /**
77  * Activity with the accessibility settings.
78  */
79 public class AccessibilitySettings extends SettingsPreferenceFragment implements DialogCreatable,
80         Preference.OnPreferenceChangeListener {
81     private static final String TAG = "AccessibilitySettings";
82
83     private static final String DEFAULT_SCREENREADER_MARKET_LINK =
84         "market://search?q=pname:com.google.android.marvin.talkback";
85
86     private static final float LARGE_FONT_SCALE = 1.3f;
87
88     private static final String SYSTEM_PROPERTY_MARKET_URL = "ro.screenreader.market";
89
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;
94
95     private static final char ENABLED_ACCESSIBILITY_SERVICES_SEPARATOR = ':';
96
97     private static final String KEY_INSTALL_ACCESSIBILITY_SERVICE_OFFERED_ONCE =
98         "key_install_accessibility_service_offered_once";
99
100     // Preference categories
101     private static final String SERVICES_CATEGORY = "services_category";
102     private static final String SYSTEM_CATEGORY = "system_category";
103
104     // Preferences
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";
116
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";
128
129     // Dialog IDs.
130     private static final int DIALOG_ID_NO_ACCESSIBILITY_SERVICES = 1;
131
132     // Auxiliary members.
133     private final static SimpleStringSplitter sStringColonSplitter =
134         new SimpleStringSplitter(ENABLED_ACCESSIBILITY_SERVICES_SEPARATOR);
135
136     private static final Set<ComponentName> sInstalledServices = new HashSet<ComponentName>();
137
138     private final Map<String, String> mLongPressTimeoutValuetoTitleMap =
139         new HashMap<String, String>();
140
141     private final Configuration mCurConfig = new Configuration();
142
143     private final PackageMonitor mSettingsPackageMonitor = new SettingsPackageMonitor();
144
145     private final Handler mHandler = new Handler() {
146         @Override
147         public void dispatchMessage(Message msg) {
148             super.dispatchMessage(msg);
149             loadInstalledServices();
150             updateServicesPreferences();
151         }
152     };
153
154     private final Uri mLockScreenRotationUri = Uri.withAppendedPath(Settings.System.CONTENT_URI,
155             Settings.System.ACCELEROMETER_ROTATION);
156
157     private final ContentObserver mSettingsContentObserver = new ContentObserver(mHandler) {
158         @Override
159         public void onChange(boolean selfChange, Uri uri) {
160             if (mLockScreenRotationUri.equals(uri)) {
161                 try {
162                     final boolean lockRotationEnabled = (Settings.System.getInt(
163                             getActivity().getContentResolver(),
164                             Settings.System.ACCELEROMETER_ROTATION) == 0);
165                     mToggleLockScreenRotationPreference.setChecked(lockRotationEnabled);
166                 } catch (SettingNotFoundException e) {
167                     /* ignore */
168                 }
169             }
170         }
171     };
172
173     // Preference controls.
174     private PreferenceCategory mServicesCategory;
175     private PreferenceCategory mSystemsCategory;
176
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;
184
185     private int mLongPressTimeoutDefault;
186
187     @Override
188     public void onCreate(Bundle icicle) {
189         super.onCreate(icicle);
190         addPreferencesFromResource(R.xml.accessibility_settings);
191         initializeAllPreferences();
192     }
193
194     @Override
195     public void onResume() {
196         super.onResume();
197         loadInstalledServices();
198         updateAllPreferences();
199         if (mServicesCategory.getPreference(0) == mNoServicesMessagePreference) {
200             offerInstallAccessibilitySerivceOnce();
201         }
202         mSettingsPackageMonitor.register(getActivity(), getActivity().getMainLooper(), false);
203         getActivity().getContentResolver().registerContentObserver(mLockScreenRotationUri, false,
204                 mSettingsContentObserver);
205     }
206
207     @Override
208     public void onPause() {
209         mSettingsPackageMonitor.unregister();
210         getContentResolver().unregisterContentObserver(mSettingsContentObserver);
211         super.onPause();
212     }
213
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));
221             return true;
222         }
223         return false;
224     }
225
226     @Override
227     public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) {
228         if (mToggleLargeTextPreference == preference) {
229             handleToggleLargeTextPreferenceClick();
230             return true;
231         } else if (mTogglePowerButtonEndsCallPreference == preference) {
232             handleTogglePowerButtonEndsCallPreferenceClick();
233             return true;
234         } else if (mToggleLockScreenRotationPreference == preference) {
235             handleLockScreenRotationPreferenceClick();
236             return true;
237         } else if (mToggleSpeakPasswordPreference == preference) {
238             handleToggleSpeakPasswordPreferenceClick();
239         }
240         return super.onPreferenceTreeClick(preferenceScreen, preference);
241     }
242
243     private void handleToggleLargeTextPreferenceClick() {
244         try {
245             mCurConfig.fontScale = mToggleLargeTextPreference.isChecked() ? LARGE_FONT_SCALE : 1;
246             ActivityManagerNative.getDefault().updatePersistentConfiguration(mCurConfig);
247         } catch (RemoteException re) {
248             /* ignore */
249         }
250     }
251
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));
258     }
259
260     private void handleLockScreenRotationPreferenceClick() {
261         try {
262             IWindowManager wm = IWindowManager.Stub.asInterface(
263                     ServiceManager.getService(Context.WINDOW_SERVICE));
264             if (!mToggleLockScreenRotationPreference.isChecked()) {
265                 wm.thawRotation();
266             } else {
267                 wm.freezeRotation(Surface.ROTATION_0);
268             }
269         } catch (RemoteException exc) {
270             Log.w(TAG, "Unable to save auto-rotate setting");
271         }
272     }
273
274     private void handleToggleSpeakPasswordPreferenceClick() {
275         Settings.Secure.putInt(getContentResolver(),
276                 Settings.Secure.ACCESSIBILITY_SPEAK_PASSWORD,
277                 mToggleSpeakPasswordPreference.isChecked() ? 1 : 0);
278     }
279
280     private void initializeAllPreferences() {
281         mServicesCategory = (PreferenceCategory) findPreference(SERVICES_CATEGORY);
282         mSystemsCategory = (PreferenceCategory) findPreference(SYSTEM_CATEGORY);
283
284         // Large text.
285         mToggleLargeTextPreference =
286             (CheckBoxPreference) findPreference(TOGGLE_LARGE_TEXT_PREFERENCE);
287
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);
294         }
295
296         // Lock screen rotation.
297         mToggleLockScreenRotationPreference =
298             (CheckBoxPreference) findPreference(TOGGLE_LOCK_SCREEN_ROTATION_PREFERENCE);
299
300         // Speak passwords.
301         mToggleSpeakPasswordPreference =
302             (CheckBoxPreference) findPreference(TOGGLE_SPEAK_PASSWORD_PREFERENCE);
303
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]);
317             }
318         }
319
320         // Script injection.
321         mToggleScriptInjectionPreference = (AccessibilityEnableScriptInjectionPreference)
322             findPreference(TOGGLE_SCRIPT_INJECTION_PREFERENCE);
323     }
324
325     private void updateAllPreferences() {
326         updateServicesPreferences();
327         updateSystemPreferences();
328     }
329
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.
334
335         // Generate.
336         mServicesCategory.removeAll();
337
338         AccessibilityManager accessibilityManager = AccessibilityManager.getInstance(getActivity());
339
340         List<AccessibilityServiceInfo> installedServices =
341             accessibilityManager.getInstalledAccessibilityServiceList();
342         Set<ComponentName> enabledServices = getEnabledServicesFromSettings(getActivity());
343
344         final boolean accessibilityEnabled = Settings.Secure.getInt(getContentResolver(),
345                 Settings.Secure.ACCESSIBILITY_ENABLED, 0) == 1;
346
347         for (int i = 0, count = installedServices.size(); i < count; ++i) {
348             AccessibilityServiceInfo info = installedServices.get(i);
349
350             PreferenceScreen preference = getPreferenceManager().createPreferenceScreen(
351                     getActivity());
352             String title = info.getResolveInfo().loadLabel(getPackageManager()).toString();
353
354             ServiceInfo serviceInfo = info.getResolveInfo().serviceInfo;
355             ComponentName componentName = new ComponentName(serviceInfo.packageName,
356                     serviceInfo.name);
357
358             preference.setKey(componentName.flattenToString());
359
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));
365             } else {
366                 preference.setSummary(getString(R.string.accessibility_service_state_off));
367             }
368
369             preference.setOrder(i);
370             preference.setFragment(ToggleAccessibilityServicePreferenceFragment.class.getName());
371             preference.setPersistent(true);
372
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);
377
378             String description = info.loadDescription(getPackageManager());
379             if (TextUtils.isEmpty(description)) {
380                 description = getString(R.string.accessibility_service_default_description);
381             }
382             extras.putString(EXTRA_SUMMARY, description);
383
384             CharSequence applicationLabel = info.getResolveInfo().loadLabel(getPackageManager());
385
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));
390
391             extras.putString(EXTRA_DISABLE_WARNING_TITLE, getString(
392                     R.string.accessibility_service_disable_warning_title,
393                     applicationLabel));
394             extras.putString(EXTRA_DISABLE_WARNING_MESSAGE, getString(
395                     R.string.accessibility_service_disable_warning_summary,
396                     applicationLabel));
397
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());
405             }
406
407             mServicesCategory.addPreference(preference);
408         }
409
410         if (mServicesCategory.getPreferenceCount() == 0) {
411             if (mNoServicesMessagePreference == null) {
412                 mNoServicesMessagePreference = new Preference(getActivity()) {
413                     @Override
414                     protected void onBindView(View view) {
415                         super.onBindView(view);
416
417                         LinearLayout containerView =
418                             (LinearLayout) view.findViewById(R.id.message_container);
419                         containerView.setGravity(Gravity.CENTER);
420
421                         TextView summaryView = (TextView) view.findViewById(R.id.summary);
422                         String title = getString(R.string.accessibility_no_services_installed);
423                         summaryView.setText(title);
424                     }
425                 };
426                 mNoServicesMessagePreference.setPersistent(false);
427                 mNoServicesMessagePreference.setLayoutResource(
428                         R.layout.text_description_preference);
429                 mNoServicesMessagePreference.setSelectable(false);
430             }
431             mServicesCategory.addPreference(mNoServicesMessagePreference);
432         }
433     }
434
435     private void updateSystemPreferences() {
436         // Large text.
437         try {
438             mCurConfig.updateFrom(ActivityManagerNative.getDefault().getConfiguration());
439         } catch (RemoteException re) {
440             /* ignore */
441         }
442         mToggleLargeTextPreference.setChecked(mCurConfig.fontScale == LARGE_FONT_SCALE);
443
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);
453         }
454
455         // Auto-rotate screen
456         final boolean lockRotationEnabled = Settings.System.getInt(getContentResolver(),
457                 Settings.System.ACCELEROMETER_ROTATION, 0) != 1;
458         mToggleLockScreenRotationPreference.setChecked(lockRotationEnabled);
459
460         // Speak passwords.
461         final boolean speakPasswordEnabled = Settings.Secure.getInt(getContentResolver(),
462                 Settings.Secure.ACCESSIBILITY_SPEAK_PASSWORD, 0) != 0;
463         mToggleSpeakPasswordPreference.setChecked(speakPasswordEnabled);
464
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));
471
472         // Script injection.
473         final boolean  scriptInjectionAllowed = (Settings.Secure.getInt(getContentResolver(),
474                 Settings.Secure.ACCESSIBILITY_SCRIPT_INJECTION, 0) == 1);
475         mToggleScriptInjectionPreference.setInjectionAllowed(scriptInjectionAllowed);
476     }
477
478     private void offerInstallAccessibilitySerivceOnce() {
479         // There is always one preference - if no services it is just a message.
480         if (mServicesCategory.getPreference(0) != mNoServicesMessagePreference) {
481             return;
482         }
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,
488                     true).commit();
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);
492         }
493     }
494
495     @Override
496     public Dialog onCreateDialog(int dialogId) {
497         switch (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);
515                             }
516                     })
517                     .setNegativeButton(android.R.string.cancel, null)
518                     .create();
519             default:
520                 return null;
521         }
522     }
523
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);
537         }
538     }
539
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 = "";
545         }
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);
555             }
556         }
557         return enabledServices;
558     }
559
560     private class SettingsPackageMonitor extends PackageMonitor {
561
562         @Override
563         public void onPackageAdded(String packageName, int uid) {
564             Message message = mHandler.obtainMessage();
565             mHandler.sendMessageDelayed(message, DELAY_UPDATE_SERVICES_MILLIS);
566         }
567
568         @Override
569         public void onPackageAppeared(String packageName, int reason) {
570             Message message = mHandler.obtainMessage();
571             mHandler.sendMessageDelayed(message, DELAY_UPDATE_SERVICES_MILLIS);
572         }
573
574         @Override
575         public void onPackageDisappeared(String packageName, int reason) {
576             Message message = mHandler.obtainMessage();
577             mHandler.sendMessageDelayed(message, DELAY_UPDATE_SERVICES_MILLIS);
578         }
579
580         @Override
581         public void onPackageRemoved(String packageName, int uid) {
582             Message message = mHandler.obtainMessage();
583             mHandler.sendMessageDelayed(message, DELAY_UPDATE_SERVICES_MILLIS);
584         }
585     }
586
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));
598         return toggleSwitch;
599     }
600
601     public static class ToggleSwitch extends Switch {
602
603         private OnBeforeCheckedChangeListener mOnBeforeListener;
604
605         public static interface OnBeforeCheckedChangeListener {
606             public boolean onBeforeCheckedChanged(ToggleSwitch toggleSwitch, boolean checked);
607         }
608
609         public ToggleSwitch(Context context) {
610             super(context);
611         }
612
613         public void setOnBeforeCheckedChangeListener(OnBeforeCheckedChangeListener listener) {
614             mOnBeforeListener = listener;
615         }
616
617         @Override
618         public void setChecked(boolean checked) {
619             if (mOnBeforeListener != null
620                     && mOnBeforeListener.onBeforeCheckedChanged(this, checked)) {
621                 return;
622             }
623             super.setChecked(checked);
624         }
625
626         public void setCheckedInternal(boolean checked) {
627             super.setChecked(checked);
628         }
629     }
630
631     public static class ToggleAccessibilityServicePreferenceFragment
632             extends SettingsPreferenceFragment implements DialogInterface.OnClickListener {
633
634         private static final int DIALOG_ID_ENABLE_WARNING = 1;
635         private static final int DIALOG_ID_DISABLE_WARNING = 2;
636
637         private String mPreferenceKey;
638
639         private ToggleSwitch mToggleSwitch;
640
641         private CharSequence mEnableWarningTitle;
642         private CharSequence mEnableWarningMessage;
643         private CharSequence mDisableWarningTitle;
644         private CharSequence mDisableWarningMessage;
645
646         private Preference mSummaryPreference;
647
648         private CharSequence mSettingsTitle;
649         private Intent mSettingsIntent;
650
651         private int mShownDialogId;
652
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;
656
657         @Override
658         public void onCreate(Bundle savedInstanceState) {
659             super.onCreate(savedInstanceState);
660             PreferenceScreen preferenceScreen = getPreferenceManager().createPreferenceScreen(
661                     getActivity());
662             setPreferenceScreen(preferenceScreen);
663             mSummaryPreference = new Preference(getActivity()) {
664                 @Override
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);
670                 }
671
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);
684                     }
685                 }
686             };
687             mSummaryPreference.setPersistent(false);
688             mSummaryPreference.setLayoutResource(R.layout.text_description_preference);
689             preferenceScreen.addPreference(mSummaryPreference);
690         }
691
692         @Override
693         public void onViewCreated(View view, Bundle savedInstanceState) {
694             super.onViewCreated(view, savedInstanceState);
695             installActionBarToggleSwitch();
696             processArguments();
697             getListView().setDivider(null);
698             getListView().setEnabled(false);
699         }
700
701         @Override
702         public void onDestroyView() {
703             getActivity().getActionBar().setCustomView(null);
704             if (mOldActivityTitle != null) {
705                 getActivity().getActionBar().setTitle(mOldActivityTitle);
706             }
707             mToggleSwitch.setOnBeforeCheckedChangeListener(null);
708             super.onDestroyView();
709         }
710
711         public void onPreferenceToggled(String preferenceKey, boolean enabled) {
712              // Parse the enabled services.
713             Set<ComponentName> enabledServices = getEnabledServicesFromSettings(getActivity());
714
715             // Determine enabled services and accessibility state.
716             ComponentName toggledService = ComponentName.unflattenFromString(preferenceKey);
717             final boolean accessibilityEnabled;
718             if (enabled) {
719                 // Enabling at least one service enables accessibility.
720                 accessibilityEnabled = true;
721                 enabledServices.add(toggledService);
722             } else {
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++;
729                     }
730                 }
731                 // Disabling the last service disables accessibility.
732                 accessibilityEnabled = enabledAndInstalledServiceCount > 1
733                     || (enabledAndInstalledServiceCount == 1
734                             && !installedServices.contains(toggledService));
735                 enabledServices.remove(toggledService);
736             }
737
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);
746             }
747             final int enabledServicesBuilderLength = enabledServicesBuilder.length();
748             if (enabledServicesBuilderLength > 0) {
749                 enabledServicesBuilder.deleteCharAt(enabledServicesBuilderLength - 1);
750             }
751             Settings.Secure.putString(getContentResolver(),
752                     Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
753                     enabledServicesBuilder.toString());
754
755             // Update accessibility enabled.
756             Settings.Secure.putInt(getContentResolver(),
757                     Settings.Secure.ACCESSIBILITY_ENABLED, accessibilityEnabled ? 1 : 0);
758         }
759
760         @Override
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);
766         }
767
768         @Override
769         public Dialog onCreateDialog(int dialogId) {
770             CharSequence title = null;
771             CharSequence message = null;
772             switch (dialogId) {
773                 case DIALOG_ID_ENABLE_WARNING:
774                     mShownDialogId = DIALOG_ID_ENABLE_WARNING;
775                     title = mEnableWarningTitle;
776                     message = mEnableWarningMessage;
777                     break;
778                 case DIALOG_ID_DISABLE_WARNING:
779                     mShownDialogId = DIALOG_ID_DISABLE_WARNING;
780                     title = mDisableWarningTitle;
781                     message = mDisableWarningMessage;
782                     break;
783                 default:
784                     throw new IllegalArgumentException();
785             }
786             return new AlertDialog.Builder(getActivity())
787                 .setTitle(title)
788                 .setIcon(android.R.drawable.ic_dialog_alert)
789                 .setMessage(message)
790                 .setCancelable(true)
791                 .setPositiveButton(android.R.string.ok, this)
792                 .setNegativeButton(android.R.string.cancel, this)
793                 .create();
794         }
795
796         @Override
797         public void onClick(DialogInterface dialog, int which) {
798             final boolean checked;
799             switch (which) {
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);
805                     break;
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);
811                     break;
812                 default:
813                     throw new IllegalArgumentException();
814             }
815         }
816
817         private void installActionBarToggleSwitch() {
818             mToggleSwitch = createAndAddActionBarToggleSwitch(getActivity());
819             mToggleSwitch.setOnBeforeCheckedChangeListener(new OnBeforeCheckedChangeListener() {
820                 @Override
821                 public boolean onBeforeCheckedChanged(ToggleSwitch toggleSwitch, boolean checked) {
822                     if (checked) {
823                         if (!TextUtils.isEmpty(mEnableWarningMessage)) {
824                             toggleSwitch.setCheckedInternal(false);
825                             getArguments().putBoolean(EXTRA_CHECKED, false);
826                             showDialog(DIALOG_ID_ENABLE_WARNING);
827                             return true;
828                         }
829                         onPreferenceToggled(mPreferenceKey, true);
830                     } else {
831                         if (!TextUtils.isEmpty(mDisableWarningMessage)) {
832                             toggleSwitch.setCheckedInternal(true);
833                             getArguments().putBoolean(EXTRA_CHECKED, true);
834                             showDialog(DIALOG_ID_DISABLE_WARNING);
835                             return true;
836                         }
837                         onPreferenceToggled(mPreferenceKey, false);
838                     }
839                     return false;
840                 }
841             });
842         }
843
844         private void processArguments() {
845             Bundle arguments = getArguments();
846
847             // Key.
848             mPreferenceKey = arguments.getString(EXTRA_PREFERENCE_KEY);
849
850             // Enabled.
851             final boolean enabled = arguments.getBoolean(EXTRA_CHECKED);
852             mToggleSwitch.setCheckedInternal(enabled);
853
854             // Title.
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);
860             }
861
862             // Summary.
863             String summary = arguments.getString(EXTRA_SUMMARY);
864             mSummaryPreference.setSummary(summary);
865
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);
876                 }
877             }
878
879             // Enable warning title.
880             mEnableWarningTitle = arguments.getCharSequence(
881                     AccessibilitySettings.EXTRA_ENABLE_WARNING_TITLE);
882
883             // Enable warning message.
884             mEnableWarningMessage = arguments.getCharSequence(
885                     AccessibilitySettings.EXTRA_ENABLE_WARNING_MESSAGE);
886
887             // Disable warning title.
888             mDisableWarningTitle = arguments.getString(
889                     AccessibilitySettings.EXTRA_DISABLE_WARNING_TITLE);
890
891             // Disable warning message.
892             mDisableWarningMessage = arguments.getString(
893                     AccessibilitySettings.EXTRA_DISABLE_WARNING_MESSAGE);
894         }
895     }
896 }