OSDN Git Service

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