• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.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.view.Gravity;
50 import android.view.KeyCharacterMap;
51 import android.view.KeyEvent;
52 import android.view.Menu;
53 import android.view.MenuInflater;
54 import android.view.MenuItem;
55 import android.view.View;
56 import android.view.accessibility.AccessibilityEvent;
57 import android.view.accessibility.AccessibilityManager;
58 import android.widget.LinearLayout;
59 import android.widget.Switch;
60 import android.widget.TextView;
61 
62 import com.android.internal.content.PackageMonitor;
63 import com.android.internal.view.RotationPolicy;
64 import com.android.settings.AccessibilitySettings.ToggleSwitch.OnBeforeCheckedChangeListener;
65 
66 import java.util.HashMap;
67 import java.util.HashSet;
68 import java.util.List;
69 import java.util.Map;
70 import java.util.Set;
71 
72 /**
73  * Activity with the accessibility settings.
74  */
75 public class AccessibilitySettings extends SettingsPreferenceFragment implements DialogCreatable,
76         Preference.OnPreferenceChangeListener {
77     private static final String DEFAULT_SCREENREADER_MARKET_LINK =
78         "market://search?q=pname:com.google.android.marvin.talkback";
79 
80     private static final float LARGE_FONT_SCALE = 1.3f;
81 
82     private static final String SYSTEM_PROPERTY_MARKET_URL = "ro.screenreader.market";
83 
84     // Timeout before we update the services if packages are added/removed since
85     // the AccessibilityManagerService has to do that processing first to generate
86     // the AccessibilityServiceInfo we need for proper presentation.
87     private static final long DELAY_UPDATE_SERVICES_MILLIS = 1000;
88 
89     private static final char ENABLED_ACCESSIBILITY_SERVICES_SEPARATOR = ':';
90 
91     private static final String KEY_INSTALL_ACCESSIBILITY_SERVICE_OFFERED_ONCE =
92         "key_install_accessibility_service_offered_once";
93 
94     // Preference categories
95     private static final String SERVICES_CATEGORY = "services_category";
96     private static final String SYSTEM_CATEGORY = "system_category";
97 
98     // Preferences
99     private static final String TOGGLE_LARGE_TEXT_PREFERENCE = "toggle_large_text_preference";
100     private static final String TOGGLE_POWER_BUTTON_ENDS_CALL_PREFERENCE =
101         "toggle_power_button_ends_call_preference";
102     private static final String TOGGLE_LOCK_SCREEN_ROTATION_PREFERENCE =
103         "toggle_lock_screen_rotation_preference";
104     private static final String TOGGLE_SPEAK_PASSWORD_PREFERENCE =
105         "toggle_speak_password_preference";
106     private static final String SELECT_LONG_PRESS_TIMEOUT_PREFERENCE =
107         "select_long_press_timeout_preference";
108     private static final String TOGGLE_SCRIPT_INJECTION_PREFERENCE =
109         "toggle_script_injection_preference";
110 
111     // Extras passed to sub-fragments.
112     private static final String EXTRA_PREFERENCE_KEY = "preference_key";
113     private static final String EXTRA_CHECKED = "checked";
114     private static final String EXTRA_TITLE = "title";
115     private static final String EXTRA_SUMMARY = "summary";
116     private static final String EXTRA_ENABLE_WARNING_TITLE = "enable_warning_title";
117     private static final String EXTRA_ENABLE_WARNING_MESSAGE = "enable_warning_message";
118     private static final String EXTRA_DISABLE_WARNING_TITLE = "disable_warning_title";
119     private static final String EXTRA_DISABLE_WARNING_MESSAGE = "disable_warning_message";
120     private static final String EXTRA_SETTINGS_TITLE = "settings_title";
121     private static final String EXTRA_SETTINGS_COMPONENT_NAME = "settings_component_name";
122 
123     // Dialog IDs.
124     private static final int DIALOG_ID_NO_ACCESSIBILITY_SERVICES = 1;
125 
126     // Auxiliary members.
127     private final static SimpleStringSplitter sStringColonSplitter =
128         new SimpleStringSplitter(ENABLED_ACCESSIBILITY_SERVICES_SEPARATOR);
129 
130     private static final Set<ComponentName> sInstalledServices = new HashSet<ComponentName>();
131 
132     private final Map<String, String> mLongPressTimeoutValuetoTitleMap =
133         new HashMap<String, String>();
134 
135     private final Configuration mCurConfig = new Configuration();
136 
137     private final PackageMonitor mSettingsPackageMonitor = new SettingsPackageMonitor();
138 
139     private final Handler mHandler = new Handler() {
140         @Override
141         public void dispatchMessage(Message msg) {
142             super.dispatchMessage(msg);
143             loadInstalledServices();
144             updateServicesPreferences();
145         }
146     };
147 
148     private final RotationPolicy.RotationPolicyListener mRotationPolicyListener =
149             new RotationPolicy.RotationPolicyListener() {
150         @Override
151         public void onChange() {
152             updateLockScreenRotationCheckbox();
153         }
154     };
155 
156     // Preference controls.
157     private PreferenceCategory mServicesCategory;
158     private PreferenceCategory mSystemsCategory;
159 
160     private CheckBoxPreference mToggleLargeTextPreference;
161     private CheckBoxPreference mTogglePowerButtonEndsCallPreference;
162     private CheckBoxPreference mToggleLockScreenRotationPreference;
163     private CheckBoxPreference mToggleSpeakPasswordPreference;
164     private ListPreference mSelectLongPressTimeoutPreference;
165     private AccessibilityEnableScriptInjectionPreference mToggleScriptInjectionPreference;
166     private Preference mNoServicesMessagePreference;
167 
168     private int mLongPressTimeoutDefault;
169 
170     @Override
onCreate(Bundle icicle)171     public void onCreate(Bundle icicle) {
172         super.onCreate(icicle);
173         addPreferencesFromResource(R.xml.accessibility_settings);
174         initializeAllPreferences();
175     }
176 
177     @Override
onResume()178     public void onResume() {
179         super.onResume();
180         loadInstalledServices();
181         updateAllPreferences();
182         if (mServicesCategory.getPreference(0) == mNoServicesMessagePreference) {
183             offerInstallAccessibilitySerivceOnce();
184         }
185         mSettingsPackageMonitor.register(getActivity(), getActivity().getMainLooper(), false);
186         RotationPolicy.registerRotationPolicyListener(getActivity(),
187                 mRotationPolicyListener);
188     }
189 
190     @Override
onPause()191     public void onPause() {
192         mSettingsPackageMonitor.unregister();
193         RotationPolicy.unregisterRotationPolicyListener(getActivity(),
194                 mRotationPolicyListener);
195         super.onPause();
196     }
197 
onPreferenceChange(Preference preference, Object newValue)198     public boolean onPreferenceChange(Preference preference, Object newValue) {
199         if (preference == mSelectLongPressTimeoutPreference) {
200             String stringValue = (String) newValue;
201             Settings.Secure.putInt(getContentResolver(),
202                     Settings.Secure.LONG_PRESS_TIMEOUT, Integer.parseInt(stringValue));
203             mSelectLongPressTimeoutPreference.setSummary(
204                     mLongPressTimeoutValuetoTitleMap.get(stringValue));
205             return true;
206         }
207         return false;
208     }
209 
210     @Override
onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference)211     public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) {
212         if (mToggleLargeTextPreference == preference) {
213             handleToggleLargeTextPreferenceClick();
214             return true;
215         } else if (mTogglePowerButtonEndsCallPreference == preference) {
216             handleTogglePowerButtonEndsCallPreferenceClick();
217             return true;
218         } else if (mToggleLockScreenRotationPreference == preference) {
219             handleLockScreenRotationPreferenceClick();
220             return true;
221         } else if (mToggleSpeakPasswordPreference == preference) {
222             handleToggleSpeakPasswordPreferenceClick();
223         }
224         return super.onPreferenceTreeClick(preferenceScreen, preference);
225     }
226 
handleToggleLargeTextPreferenceClick()227     private void handleToggleLargeTextPreferenceClick() {
228         try {
229             mCurConfig.fontScale = mToggleLargeTextPreference.isChecked() ? LARGE_FONT_SCALE : 1;
230             ActivityManagerNative.getDefault().updatePersistentConfiguration(mCurConfig);
231         } catch (RemoteException re) {
232             /* ignore */
233         }
234     }
235 
handleTogglePowerButtonEndsCallPreferenceClick()236     private void handleTogglePowerButtonEndsCallPreferenceClick() {
237         Settings.Secure.putInt(getContentResolver(),
238                 Settings.Secure.INCALL_POWER_BUTTON_BEHAVIOR,
239                 (mTogglePowerButtonEndsCallPreference.isChecked()
240                         ? Settings.Secure.INCALL_POWER_BUTTON_BEHAVIOR_HANGUP
241                         : Settings.Secure.INCALL_POWER_BUTTON_BEHAVIOR_SCREEN_OFF));
242     }
243 
handleLockScreenRotationPreferenceClick()244     private void handleLockScreenRotationPreferenceClick() {
245         RotationPolicy.setRotationLockForAccessibility(getActivity(),
246                 !mToggleLockScreenRotationPreference.isChecked());
247     }
248 
handleToggleSpeakPasswordPreferenceClick()249     private void handleToggleSpeakPasswordPreferenceClick() {
250         Settings.Secure.putInt(getContentResolver(),
251                 Settings.Secure.ACCESSIBILITY_SPEAK_PASSWORD,
252                 mToggleSpeakPasswordPreference.isChecked() ? 1 : 0);
253     }
254 
initializeAllPreferences()255     private void initializeAllPreferences() {
256         mServicesCategory = (PreferenceCategory) findPreference(SERVICES_CATEGORY);
257         mSystemsCategory = (PreferenceCategory) findPreference(SYSTEM_CATEGORY);
258 
259         // Large text.
260         mToggleLargeTextPreference =
261             (CheckBoxPreference) findPreference(TOGGLE_LARGE_TEXT_PREFERENCE);
262 
263         // Power button ends calls.
264         mTogglePowerButtonEndsCallPreference =
265             (CheckBoxPreference) findPreference(TOGGLE_POWER_BUTTON_ENDS_CALL_PREFERENCE);
266         if (!KeyCharacterMap.deviceHasKey(KeyEvent.KEYCODE_POWER)
267                 || !Utils.isVoiceCapable(getActivity())) {
268             mSystemsCategory.removePreference(mTogglePowerButtonEndsCallPreference);
269         }
270 
271         // Lock screen rotation.
272         mToggleLockScreenRotationPreference =
273             (CheckBoxPreference) findPreference(TOGGLE_LOCK_SCREEN_ROTATION_PREFERENCE);
274 
275         // Speak passwords.
276         mToggleSpeakPasswordPreference =
277             (CheckBoxPreference) findPreference(TOGGLE_SPEAK_PASSWORD_PREFERENCE);
278 
279         // Long press timeout.
280         mSelectLongPressTimeoutPreference =
281             (ListPreference) findPreference(SELECT_LONG_PRESS_TIMEOUT_PREFERENCE);
282         mSelectLongPressTimeoutPreference.setOnPreferenceChangeListener(this);
283         if (mLongPressTimeoutValuetoTitleMap.size() == 0) {
284             String[] timeoutValues = getResources().getStringArray(
285                     R.array.long_press_timeout_selector_values);
286             mLongPressTimeoutDefault = Integer.parseInt(timeoutValues[0]);
287             String[] timeoutTitles = getResources().getStringArray(
288                     R.array.long_press_timeout_selector_titles);
289             final int timeoutValueCount = timeoutValues.length;
290             for (int i = 0; i < timeoutValueCount; i++) {
291                mLongPressTimeoutValuetoTitleMap.put(timeoutValues[i], timeoutTitles[i]);
292             }
293         }
294 
295         // Script injection.
296         mToggleScriptInjectionPreference = (AccessibilityEnableScriptInjectionPreference)
297             findPreference(TOGGLE_SCRIPT_INJECTION_PREFERENCE);
298     }
299 
updateAllPreferences()300     private void updateAllPreferences() {
301         updateServicesPreferences();
302         updateSystemPreferences();
303     }
304 
updateServicesPreferences()305     private void updateServicesPreferences() {
306         // Since services category is auto generated we have to do a pass
307         // to generate it since services can come and go and then based on
308         // the global accessibility state to decided whether it is enabled.
309 
310         // Generate.
311         mServicesCategory.removeAll();
312 
313         AccessibilityManager accessibilityManager = AccessibilityManager.getInstance(getActivity());
314 
315         List<AccessibilityServiceInfo> installedServices =
316             accessibilityManager.getInstalledAccessibilityServiceList();
317         Set<ComponentName> enabledServices = getEnabledServicesFromSettings(getActivity());
318 
319         final boolean accessibilityEnabled = Settings.Secure.getInt(getContentResolver(),
320                 Settings.Secure.ACCESSIBILITY_ENABLED, 0) == 1;
321 
322         for (int i = 0, count = installedServices.size(); i < count; ++i) {
323             AccessibilityServiceInfo info = installedServices.get(i);
324 
325             PreferenceScreen preference = getPreferenceManager().createPreferenceScreen(
326                     getActivity());
327             String title = info.getResolveInfo().loadLabel(getPackageManager()).toString();
328 
329             ServiceInfo serviceInfo = info.getResolveInfo().serviceInfo;
330             ComponentName componentName = new ComponentName(serviceInfo.packageName,
331                     serviceInfo.name);
332 
333             preference.setKey(componentName.flattenToString());
334 
335             preference.setTitle(title);
336             final boolean serviceEnabled = accessibilityEnabled
337                 && enabledServices.contains(componentName);
338             if (serviceEnabled) {
339                 preference.setSummary(getString(R.string.accessibility_service_state_on));
340             } else {
341                 preference.setSummary(getString(R.string.accessibility_service_state_off));
342             }
343 
344             preference.setOrder(i);
345             preference.setFragment(ToggleAccessibilityServicePreferenceFragment.class.getName());
346             preference.setPersistent(true);
347 
348             Bundle extras = preference.getExtras();
349             extras.putString(EXTRA_PREFERENCE_KEY, preference.getKey());
350             extras.putBoolean(EXTRA_CHECKED, serviceEnabled);
351             extras.putString(EXTRA_TITLE, title);
352 
353             String description = info.loadDescription(getPackageManager());
354             if (TextUtils.isEmpty(description)) {
355                 description = getString(R.string.accessibility_service_default_description);
356             }
357             extras.putString(EXTRA_SUMMARY, description);
358 
359             CharSequence applicationLabel = info.getResolveInfo().loadLabel(getPackageManager());
360 
361             extras.putString(EXTRA_ENABLE_WARNING_TITLE, getString(
362                     R.string.accessibility_service_security_warning_title, applicationLabel));
363             extras.putString(EXTRA_ENABLE_WARNING_MESSAGE, getString(
364                     R.string.accessibility_service_security_warning_summary, applicationLabel));
365 
366             extras.putString(EXTRA_DISABLE_WARNING_TITLE, getString(
367                     R.string.accessibility_service_disable_warning_title,
368                     applicationLabel));
369             extras.putString(EXTRA_DISABLE_WARNING_MESSAGE, getString(
370                     R.string.accessibility_service_disable_warning_summary,
371                     applicationLabel));
372 
373             String settingsClassName = info.getSettingsActivityName();
374             if (!TextUtils.isEmpty(settingsClassName)) {
375                 extras.putString(EXTRA_SETTINGS_TITLE,
376                         getString(R.string.accessibility_menu_item_settings));
377                 extras.putString(EXTRA_SETTINGS_COMPONENT_NAME,
378                         new ComponentName(info.getResolveInfo().serviceInfo.packageName,
379                                 settingsClassName).flattenToString());
380             }
381 
382             mServicesCategory.addPreference(preference);
383         }
384 
385         if (mServicesCategory.getPreferenceCount() == 0) {
386             if (mNoServicesMessagePreference == null) {
387                 mNoServicesMessagePreference = new Preference(getActivity()) {
388                     @Override
389                     protected void onBindView(View view) {
390                         super.onBindView(view);
391 
392                         LinearLayout containerView =
393                             (LinearLayout) view.findViewById(R.id.message_container);
394                         containerView.setGravity(Gravity.CENTER);
395 
396                         TextView summaryView = (TextView) view.findViewById(R.id.summary);
397                         String title = getString(R.string.accessibility_no_services_installed);
398                         summaryView.setText(title);
399                     }
400                 };
401                 mNoServicesMessagePreference.setPersistent(false);
402                 mNoServicesMessagePreference.setLayoutResource(
403                         R.layout.text_description_preference);
404                 mNoServicesMessagePreference.setSelectable(false);
405             }
406             mServicesCategory.addPreference(mNoServicesMessagePreference);
407         }
408     }
409 
updateSystemPreferences()410     private void updateSystemPreferences() {
411         // Large text.
412         try {
413             mCurConfig.updateFrom(ActivityManagerNative.getDefault().getConfiguration());
414         } catch (RemoteException re) {
415             /* ignore */
416         }
417         mToggleLargeTextPreference.setChecked(mCurConfig.fontScale == LARGE_FONT_SCALE);
418 
419         // Power button ends calls.
420         if (KeyCharacterMap.deviceHasKey(KeyEvent.KEYCODE_POWER)
421                 && Utils.isVoiceCapable(getActivity())) {
422             final int incallPowerBehavior = Settings.Secure.getInt(getContentResolver(),
423                     Settings.Secure.INCALL_POWER_BUTTON_BEHAVIOR,
424                     Settings.Secure.INCALL_POWER_BUTTON_BEHAVIOR_DEFAULT);
425             final boolean powerButtonEndsCall =
426                 (incallPowerBehavior == Settings.Secure.INCALL_POWER_BUTTON_BEHAVIOR_HANGUP);
427             mTogglePowerButtonEndsCallPreference.setChecked(powerButtonEndsCall);
428         }
429 
430         // Auto-rotate screen
431         updateLockScreenRotationCheckbox();
432 
433         // Speak passwords.
434         final boolean speakPasswordEnabled = Settings.Secure.getInt(getContentResolver(),
435                 Settings.Secure.ACCESSIBILITY_SPEAK_PASSWORD, 0) != 0;
436         mToggleSpeakPasswordPreference.setChecked(speakPasswordEnabled);
437 
438         // Long press timeout.
439         final int longPressTimeout = Settings.Secure.getInt(getContentResolver(),
440                 Settings.Secure.LONG_PRESS_TIMEOUT, mLongPressTimeoutDefault);
441         String value = String.valueOf(longPressTimeout);
442         mSelectLongPressTimeoutPreference.setValue(value);
443         mSelectLongPressTimeoutPreference.setSummary(mLongPressTimeoutValuetoTitleMap.get(value));
444 
445         // Script injection.
446         final boolean  scriptInjectionAllowed = (Settings.Secure.getInt(getContentResolver(),
447                 Settings.Secure.ACCESSIBILITY_SCRIPT_INJECTION, 0) == 1);
448         mToggleScriptInjectionPreference.setInjectionAllowed(scriptInjectionAllowed);
449     }
450 
updateLockScreenRotationCheckbox()451     private void updateLockScreenRotationCheckbox() {
452         Context context = getActivity();
453         if (context != null) {
454             mToggleLockScreenRotationPreference.setChecked(
455                     !RotationPolicy.isRotationLocked(context));
456         }
457     }
458 
offerInstallAccessibilitySerivceOnce()459     private void offerInstallAccessibilitySerivceOnce() {
460         // There is always one preference - if no services it is just a message.
461         if (mServicesCategory.getPreference(0) != mNoServicesMessagePreference) {
462             return;
463         }
464         SharedPreferences preferences = getActivity().getPreferences(Context.MODE_PRIVATE);
465         final boolean offerInstallService = !preferences.getBoolean(
466                 KEY_INSTALL_ACCESSIBILITY_SERVICE_OFFERED_ONCE, false);
467         if (offerInstallService) {
468             preferences.edit().putBoolean(KEY_INSTALL_ACCESSIBILITY_SERVICE_OFFERED_ONCE,
469                     true).commit();
470             // Notify user that they do not have any accessibility
471             // services installed and direct them to Market to get TalkBack.
472             showDialog(DIALOG_ID_NO_ACCESSIBILITY_SERVICES);
473         }
474     }
475 
476     @Override
onCreateDialog(int dialogId)477     public Dialog onCreateDialog(int dialogId) {
478         switch (dialogId) {
479             case DIALOG_ID_NO_ACCESSIBILITY_SERVICES:
480                 return new AlertDialog.Builder(getActivity())
481                     .setTitle(R.string.accessibility_service_no_apps_title)
482                     .setMessage(R.string.accessibility_service_no_apps_message)
483                     .setPositiveButton(android.R.string.ok,
484                         new DialogInterface.OnClickListener() {
485                             public void onClick(DialogInterface dialog, int which) {
486                                 // dismiss the dialog before launching the activity otherwise
487                                 // the dialog removal occurs after onSaveInstanceState which
488                                 // triggers an exception
489                                 removeDialog(DIALOG_ID_NO_ACCESSIBILITY_SERVICES);
490                                 String screenreaderMarketLink = SystemProperties.get(
491                                         SYSTEM_PROPERTY_MARKET_URL,
492                                         DEFAULT_SCREENREADER_MARKET_LINK);
493                                 Uri marketUri = Uri.parse(screenreaderMarketLink);
494                                 Intent marketIntent = new Intent(Intent.ACTION_VIEW, marketUri);
495                                 startActivity(marketIntent);
496                             }
497                     })
498                     .setNegativeButton(android.R.string.cancel, null)
499                     .create();
500             default:
501                 return null;
502         }
503     }
504 
505     private void loadInstalledServices() {
506         List<AccessibilityServiceInfo> installedServiceInfos =
507             AccessibilityManager.getInstance(getActivity())
508                 .getInstalledAccessibilityServiceList();
509         Set<ComponentName> installedServices = sInstalledServices;
510         installedServices.clear();
511         final int installedServiceInfoCount = installedServiceInfos.size();
512         for (int i = 0; i < installedServiceInfoCount; i++) {
513             ResolveInfo resolveInfo = installedServiceInfos.get(i).getResolveInfo();
514             ComponentName installedService = new ComponentName(
515                     resolveInfo.serviceInfo.packageName,
516                     resolveInfo.serviceInfo.name);
517             installedServices.add(installedService);
518         }
519     }
520 
521     private static Set<ComponentName> getEnabledServicesFromSettings(Context context) {
522         String enabledServicesSetting = Settings.Secure.getString(context.getContentResolver(),
523                 Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES);
524         if (enabledServicesSetting == null) {
525             enabledServicesSetting = "";
526         }
527         Set<ComponentName> enabledServices = new HashSet<ComponentName>();
528         SimpleStringSplitter colonSplitter = sStringColonSplitter;
529         colonSplitter.setString(enabledServicesSetting);
530         while (colonSplitter.hasNext()) {
531             String componentNameString = colonSplitter.next();
532             ComponentName enabledService = ComponentName.unflattenFromString(
533                     componentNameString);
534             if (enabledService != null) {
535                 enabledServices.add(enabledService);
536             }
537         }
538         return enabledServices;
539     }
540 
541     private class SettingsPackageMonitor extends PackageMonitor {
542 
543         @Override
544         public void onPackageAdded(String packageName, int uid) {
545             Message message = mHandler.obtainMessage();
546             mHandler.sendMessageDelayed(message, DELAY_UPDATE_SERVICES_MILLIS);
547         }
548 
549         @Override
550         public void onPackageAppeared(String packageName, int reason) {
551             Message message = mHandler.obtainMessage();
552             mHandler.sendMessageDelayed(message, DELAY_UPDATE_SERVICES_MILLIS);
553         }
554 
555         @Override
556         public void onPackageDisappeared(String packageName, int reason) {
557             Message message = mHandler.obtainMessage();
558             mHandler.sendMessageDelayed(message, DELAY_UPDATE_SERVICES_MILLIS);
559         }
560 
561         @Override
562         public void onPackageRemoved(String packageName, int uid) {
563             Message message = mHandler.obtainMessage();
564             mHandler.sendMessageDelayed(message, DELAY_UPDATE_SERVICES_MILLIS);
565         }
566     }
567 
568     private static ToggleSwitch createAndAddActionBarToggleSwitch(Activity activity) {
569         ToggleSwitch toggleSwitch = new ToggleSwitch(activity);
570         final int padding = activity.getResources().getDimensionPixelSize(
571                 R.dimen.action_bar_switch_padding);
572         toggleSwitch.setPadding(0, 0, padding, 0);
573         activity.getActionBar().setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM,
574                 ActionBar.DISPLAY_SHOW_CUSTOM);
575         activity.getActionBar().setCustomView(toggleSwitch,
576                 new ActionBar.LayoutParams(ActionBar.LayoutParams.WRAP_CONTENT,
577                         ActionBar.LayoutParams.WRAP_CONTENT,
578                         Gravity.CENTER_VERTICAL | Gravity.RIGHT));
579         return toggleSwitch;
580     }
581 
582     public static class ToggleSwitch extends Switch {
583 
584         private OnBeforeCheckedChangeListener mOnBeforeListener;
585 
586         public static interface OnBeforeCheckedChangeListener {
587             public boolean onBeforeCheckedChanged(ToggleSwitch toggleSwitch, boolean checked);
588         }
589 
590         public ToggleSwitch(Context context) {
591             super(context);
592         }
593 
594         public void setOnBeforeCheckedChangeListener(OnBeforeCheckedChangeListener listener) {
595             mOnBeforeListener = listener;
596         }
597 
598         @Override
599         public void setChecked(boolean checked) {
600             if (mOnBeforeListener != null
601                     && mOnBeforeListener.onBeforeCheckedChanged(this, checked)) {
602                 return;
603             }
604             super.setChecked(checked);
605         }
606 
607         public void setCheckedInternal(boolean checked) {
608             super.setChecked(checked);
609         }
610     }
611 
612     public static class ToggleAccessibilityServicePreferenceFragment
613             extends SettingsPreferenceFragment implements DialogInterface.OnClickListener {
614 
615         private static final int DIALOG_ID_ENABLE_WARNING = 1;
616         private static final int DIALOG_ID_DISABLE_WARNING = 2;
617 
618         private String mPreferenceKey;
619 
620         private ToggleSwitch mToggleSwitch;
621 
622         private CharSequence mEnableWarningTitle;
623         private CharSequence mEnableWarningMessage;
624         private CharSequence mDisableWarningTitle;
625         private CharSequence mDisableWarningMessage;
626 
627         private Preference mSummaryPreference;
628 
629         private CharSequence mSettingsTitle;
630         private Intent mSettingsIntent;
631 
632         private int mShownDialogId;
633 
634         // TODO: Showing sub-sub fragment does not handle the activity title
635         //       so we do it but this is wrong. Do a real fix when there is time.
636         private CharSequence mOldActivityTitle;
637 
638         @Override
639         public void onCreate(Bundle savedInstanceState) {
640             super.onCreate(savedInstanceState);
641             PreferenceScreen preferenceScreen = getPreferenceManager().createPreferenceScreen(
642                     getActivity());
643             setPreferenceScreen(preferenceScreen);
644             mSummaryPreference = new Preference(getActivity()) {
645                 @Override
646                 protected void onBindView(View view) {
647                     super.onBindView(view);
648                     TextView summaryView = (TextView) view.findViewById(R.id.summary);
649                     summaryView.setText(getSummary());
650                     sendAccessibilityEvent(summaryView);
651                 }
652 
653                 private void sendAccessibilityEvent(View view) {
654                     // Since the view is still not attached we create, populate,
655                     // and send the event directly since we do not know when it
656                     // will be attached and posting commands is not as clean.
657                     AccessibilityManager accessibilityManager =
658                         AccessibilityManager.getInstance(getActivity());
659                     if (accessibilityManager.isEnabled()) {
660                         AccessibilityEvent event = AccessibilityEvent.obtain();
661                         event.setEventType(AccessibilityEvent.TYPE_VIEW_FOCUSED);
662                         view.onInitializeAccessibilityEvent(event);
663                         view.dispatchPopulateAccessibilityEvent(event);
664                         accessibilityManager.sendAccessibilityEvent(event);
665                     }
666                 }
667             };
668             mSummaryPreference.setPersistent(false);
669             mSummaryPreference.setLayoutResource(R.layout.text_description_preference);
670             preferenceScreen.addPreference(mSummaryPreference);
671         }
672 
673         @Override
674         public void onViewCreated(View view, Bundle savedInstanceState) {
675             super.onViewCreated(view, savedInstanceState);
676             installActionBarToggleSwitch();
677             processArguments();
678             getListView().setDivider(null);
679             getListView().setEnabled(false);
680         }
681 
682         @Override
683         public void onDestroyView() {
684             getActivity().getActionBar().setCustomView(null);
685             if (mOldActivityTitle != null) {
686                 getActivity().getActionBar().setTitle(mOldActivityTitle);
687             }
688             mToggleSwitch.setOnBeforeCheckedChangeListener(null);
689             super.onDestroyView();
690         }
691 
692         public void onPreferenceToggled(String preferenceKey, boolean enabled) {
693              // Parse the enabled services.
694             Set<ComponentName> enabledServices = getEnabledServicesFromSettings(getActivity());
695 
696             // Determine enabled services and accessibility state.
697             ComponentName toggledService = ComponentName.unflattenFromString(preferenceKey);
698             final boolean accessibilityEnabled;
699             if (enabled) {
700                 // Enabling at least one service enables accessibility.
701                 accessibilityEnabled = true;
702                 enabledServices.add(toggledService);
703             } else {
704                 // Check how many enabled and installed services are present.
705                 int enabledAndInstalledServiceCount = 0;
706                 Set<ComponentName> installedServices = sInstalledServices;
707                 for (ComponentName enabledService : enabledServices) {
708                     if (installedServices.contains(enabledService)) {
709                         enabledAndInstalledServiceCount++;
710                     }
711                 }
712                 // Disabling the last service disables accessibility.
713                 accessibilityEnabled = enabledAndInstalledServiceCount > 1
714                     || (enabledAndInstalledServiceCount == 1
715                             && !installedServices.contains(toggledService));
716                 enabledServices.remove(toggledService);
717             }
718 
719             // Update the enabled services setting.
720             StringBuilder enabledServicesBuilder = new StringBuilder();
721             // Keep the enabled services even if they are not installed since we have
722             // no way to know whether the application restore process has completed.
723             // In general the system should be responsible for the clean up not settings.
724             for (ComponentName enabledService : enabledServices) {
725                 enabledServicesBuilder.append(enabledService.flattenToString());
726                 enabledServicesBuilder.append(ENABLED_ACCESSIBILITY_SERVICES_SEPARATOR);
727             }
728             final int enabledServicesBuilderLength = enabledServicesBuilder.length();
729             if (enabledServicesBuilderLength > 0) {
730                 enabledServicesBuilder.deleteCharAt(enabledServicesBuilderLength - 1);
731             }
732             Settings.Secure.putString(getContentResolver(),
733                     Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
734                     enabledServicesBuilder.toString());
735 
736             // Update accessibility enabled.
737             Settings.Secure.putInt(getContentResolver(),
738                     Settings.Secure.ACCESSIBILITY_ENABLED, accessibilityEnabled ? 1 : 0);
739         }
740 
741         @Override
742         public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
743             super.onCreateOptionsMenu(menu, inflater);
744             MenuItem menuItem = menu.add(mSettingsTitle);
745             menuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
746             menuItem.setIntent(mSettingsIntent);
747         }
748 
749         @Override
750         public Dialog onCreateDialog(int dialogId) {
751             CharSequence title = null;
752             CharSequence message = null;
753             switch (dialogId) {
754                 case DIALOG_ID_ENABLE_WARNING:
755                     mShownDialogId = DIALOG_ID_ENABLE_WARNING;
756                     title = mEnableWarningTitle;
757                     message = mEnableWarningMessage;
758                     break;
759                 case DIALOG_ID_DISABLE_WARNING:
760                     mShownDialogId = DIALOG_ID_DISABLE_WARNING;
761                     title = mDisableWarningTitle;
762                     message = mDisableWarningMessage;
763                     break;
764                 default:
765                     throw new IllegalArgumentException();
766             }
767             return new AlertDialog.Builder(getActivity())
768                 .setTitle(title)
769                 .setIcon(android.R.drawable.ic_dialog_alert)
770                 .setMessage(message)
771                 .setCancelable(true)
772                 .setPositiveButton(android.R.string.ok, this)
773                 .setNegativeButton(android.R.string.cancel, this)
774                 .create();
775         }
776 
777         @Override
778         public void onClick(DialogInterface dialog, int which) {
779             final boolean checked;
780             switch (which) {
781                 case DialogInterface.BUTTON_POSITIVE:
782                     checked = (mShownDialogId == DIALOG_ID_ENABLE_WARNING);
783                     mToggleSwitch.setCheckedInternal(checked);
784                     getArguments().putBoolean(EXTRA_CHECKED, checked);
785                     onPreferenceToggled(mPreferenceKey, checked);
786                     break;
787                 case DialogInterface.BUTTON_NEGATIVE:
788                     checked = (mShownDialogId == DIALOG_ID_DISABLE_WARNING);
789                     mToggleSwitch.setCheckedInternal(checked);
790                     getArguments().putBoolean(EXTRA_CHECKED, checked);
791                     onPreferenceToggled(mPreferenceKey, checked);
792                     break;
793                 default:
794                     throw new IllegalArgumentException();
795             }
796         }
797 
798         private void installActionBarToggleSwitch() {
799             mToggleSwitch = createAndAddActionBarToggleSwitch(getActivity());
800             mToggleSwitch.setOnBeforeCheckedChangeListener(new OnBeforeCheckedChangeListener() {
801                 @Override
802                 public boolean onBeforeCheckedChanged(ToggleSwitch toggleSwitch, boolean checked) {
803                     if (checked) {
804                         if (!TextUtils.isEmpty(mEnableWarningMessage)) {
805                             toggleSwitch.setCheckedInternal(false);
806                             getArguments().putBoolean(EXTRA_CHECKED, false);
807                             showDialog(DIALOG_ID_ENABLE_WARNING);
808                             return true;
809                         }
810                         onPreferenceToggled(mPreferenceKey, true);
811                     } else {
812                         if (!TextUtils.isEmpty(mDisableWarningMessage)) {
813                             toggleSwitch.setCheckedInternal(true);
814                             getArguments().putBoolean(EXTRA_CHECKED, true);
815                             showDialog(DIALOG_ID_DISABLE_WARNING);
816                             return true;
817                         }
818                         onPreferenceToggled(mPreferenceKey, false);
819                     }
820                     return false;
821                 }
822             });
823         }
824 
825         private void processArguments() {
826             Bundle arguments = getArguments();
827 
828             // Key.
829             mPreferenceKey = arguments.getString(EXTRA_PREFERENCE_KEY);
830 
831             // Enabled.
832             final boolean enabled = arguments.getBoolean(EXTRA_CHECKED);
833             mToggleSwitch.setCheckedInternal(enabled);
834 
835             // Title.
836             PreferenceActivity activity = (PreferenceActivity) getActivity();
837             if (!activity.onIsMultiPane() || activity.onIsHidingHeaders()) {
838                 mOldActivityTitle = getActivity().getTitle();
839                 String title = arguments.getString(EXTRA_TITLE);
840                 getActivity().getActionBar().setTitle(title);
841             }
842 
843             // Summary.
844             String summary = arguments.getString(EXTRA_SUMMARY);
845             mSummaryPreference.setSummary(summary);
846 
847             // Settings title and intent.
848             String settingsTitle = arguments.getString(EXTRA_SETTINGS_TITLE);
849             String settingsComponentName = arguments.getString(EXTRA_SETTINGS_COMPONENT_NAME);
850             if (!TextUtils.isEmpty(settingsTitle) && !TextUtils.isEmpty(settingsComponentName)) {
851                 Intent settingsIntent = new Intent(Intent.ACTION_MAIN).setComponent(
852                         ComponentName.unflattenFromString(settingsComponentName.toString()));
853                 if (!getPackageManager().queryIntentActivities(settingsIntent, 0).isEmpty()) {
854                     mSettingsTitle = settingsTitle;
855                     mSettingsIntent = settingsIntent;
856                     setHasOptionsMenu(true);
857                 }
858             }
859 
860             // Enable warning title.
861             mEnableWarningTitle = arguments.getCharSequence(
862                     AccessibilitySettings.EXTRA_ENABLE_WARNING_TITLE);
863 
864             // Enable warning message.
865             mEnableWarningMessage = arguments.getCharSequence(
866                     AccessibilitySettings.EXTRA_ENABLE_WARNING_MESSAGE);
867 
868             // Disable warning title.
869             mDisableWarningTitle = arguments.getString(
870                     AccessibilitySettings.EXTRA_DISABLE_WARNING_TITLE);
871 
872             // Disable warning message.
873             mDisableWarningMessage = arguments.getString(
874                     AccessibilitySettings.EXTRA_DISABLE_WARNING_MESSAGE);
875         }
876     }
877 }
878