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