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
17package com.android.settings.accessibility;
18
19import android.accessibilityservice.AccessibilityServiceInfo;
20import android.app.ActivityManagerNative;
21import android.app.AlertDialog;
22import android.app.Dialog;
23import android.content.ActivityNotFoundException;
24import android.content.ComponentName;
25import android.content.Context;
26import android.content.DialogInterface;
27import android.content.Intent;
28import android.content.SharedPreferences;
29import android.content.pm.ResolveInfo;
30import android.content.pm.ServiceInfo;
31import android.content.res.Configuration;
32import android.net.Uri;
33import android.os.Bundle;
34import android.os.Handler;
35import android.os.RemoteException;
36import android.os.SystemProperties;
37import android.preference.CheckBoxPreference;
38import android.preference.ListPreference;
39import android.preference.Preference;
40import android.preference.PreferenceCategory;
41import android.preference.PreferenceScreen;
42import android.provider.Settings;
43import android.text.TextUtils;
44import android.text.TextUtils.SimpleStringSplitter;
45import android.util.Log;
46import android.view.KeyCharacterMap;
47import android.view.KeyEvent;
48import android.view.View;
49import android.view.accessibility.AccessibilityManager;
50import android.widget.TextView;
51
52import com.android.internal.content.PackageMonitor;
53import com.android.internal.view.RotationPolicy;
54import com.android.internal.view.RotationPolicy.RotationPolicyListener;
55import com.android.settings.DialogCreatable;
56import com.android.settings.R;
57import com.android.settings.SettingsPreferenceFragment;
58import com.android.settings.Utils;
59
60import java.util.HashMap;
61import java.util.HashSet;
62import java.util.List;
63import java.util.Map;
64import java.util.Set;
65
66/**
67 * Activity with the accessibility settings.
68 */
69public class AccessibilitySettings extends SettingsPreferenceFragment implements DialogCreatable,
70        Preference.OnPreferenceChangeListener {
71    private static final String LOG_TAG = "AccessibilitySettings";
72
73    private static final String DEFAULT_SCREENREADER_MARKET_LINK =
74            "market://search?q=pname:com.google.android.marvin.talkback";
75
76    private static final float LARGE_FONT_SCALE = 1.3f;
77
78    private static final String SYSTEM_PROPERTY_MARKET_URL = "ro.screenreader.market";
79
80    static final char ENABLED_ACCESSIBILITY_SERVICES_SEPARATOR = ':';
81
82    private static final String KEY_INSTALL_ACCESSIBILITY_SERVICE_OFFERED_ONCE =
83            "key_install_accessibility_service_offered_once";
84
85    // Preference categories
86    private static final String SERVICES_CATEGORY = "services_category";
87    private static final String SYSTEM_CATEGORY = "system_category";
88
89    // Preferences
90    private static final String TOGGLE_LARGE_TEXT_PREFERENCE =
91            "toggle_large_text_preference";
92    private static final String TOGGLE_POWER_BUTTON_ENDS_CALL_PREFERENCE =
93            "toggle_power_button_ends_call_preference";
94    private static final String TOGGLE_LOCK_SCREEN_ROTATION_PREFERENCE =
95            "toggle_lock_screen_rotation_preference";
96    private static final String TOGGLE_SPEAK_PASSWORD_PREFERENCE =
97            "toggle_speak_password_preference";
98    private static final String SELECT_LONG_PRESS_TIMEOUT_PREFERENCE =
99            "select_long_press_timeout_preference";
100    private static final String ENABLE_ACCESSIBILITY_GESTURE_PREFERENCE_SCREEN =
101            "enable_global_gesture_preference_screen";
102    private static final String CAPTIONING_PREFERENCE_SCREEN =
103            "captioning_preference_screen";
104    private static final String DISPLAY_MAGNIFICATION_PREFERENCE_SCREEN =
105            "screen_magnification_preference_screen";
106
107    // Extras passed to sub-fragments.
108    static final String EXTRA_PREFERENCE_KEY = "preference_key";
109    static final String EXTRA_CHECKED = "checked";
110    static final String EXTRA_TITLE = "title";
111    static final String EXTRA_SUMMARY = "summary";
112    static final String EXTRA_SETTINGS_TITLE = "settings_title";
113    static final String EXTRA_COMPONENT_NAME = "component_name";
114    static final String EXTRA_SETTINGS_COMPONENT_NAME = "settings_component_name";
115
116    // Timeout before we update the services if packages are added/removed
117    // since the AccessibilityManagerService has to do that processing first
118    // to generate the AccessibilityServiceInfo we need for proper
119    // presentation.
120    private static final long DELAY_UPDATE_SERVICES_MILLIS = 1000;
121
122    // Dialog IDs.
123    private static final int DIALOG_ID_NO_ACCESSIBILITY_SERVICES = 1;
124
125    // Auxiliary members.
126    final static SimpleStringSplitter sStringColonSplitter =
127            new SimpleStringSplitter(ENABLED_ACCESSIBILITY_SERVICES_SEPARATOR);
128
129    static final Set<ComponentName> sInstalledServices = new HashSet<ComponentName>();
130
131    private final Map<String, String> mLongPressTimeoutValuetoTitleMap =
132            new HashMap<String, String>();
133
134    private final Configuration mCurConfig = new Configuration();
135
136    private final Handler mHandler = new Handler();
137
138    private final Runnable mUpdateRunnable = new Runnable() {
139        @Override
140        public void run() {
141            loadInstalledServices();
142            updateServicesPreferences();
143        }
144    };
145
146    private final PackageMonitor mSettingsPackageMonitor = new PackageMonitor() {
147        @Override
148        public void onPackageAdded(String packageName, int uid) {
149            sendUpdate();
150        }
151
152        @Override
153        public void onPackageAppeared(String packageName, int reason) {
154            sendUpdate();
155        }
156
157        @Override
158        public void onPackageDisappeared(String packageName, int reason) {
159            sendUpdate();
160        }
161
162        @Override
163        public void onPackageRemoved(String packageName, int uid) {
164            sendUpdate();
165        }
166
167        private void sendUpdate() {
168            mHandler.postDelayed(mUpdateRunnable, DELAY_UPDATE_SERVICES_MILLIS);
169        }
170    };
171
172    private final SettingsContentObserver mSettingsContentObserver =
173            new SettingsContentObserver(mHandler) {
174                @Override
175                public void onChange(boolean selfChange, Uri uri) {
176                    loadInstalledServices();
177                    updateServicesPreferences();
178                }
179            };
180
181    private final RotationPolicyListener mRotationPolicyListener = new RotationPolicyListener() {
182        @Override
183        public void onChange() {
184            updateLockScreenRotationCheckbox();
185        }
186    };
187
188    // Preference controls.
189    private PreferenceCategory mServicesCategory;
190    private PreferenceCategory mSystemsCategory;
191
192    private CheckBoxPreference mToggleLargeTextPreference;
193    private CheckBoxPreference mTogglePowerButtonEndsCallPreference;
194    private CheckBoxPreference mToggleLockScreenRotationPreference;
195    private CheckBoxPreference mToggleSpeakPasswordPreference;
196    private ListPreference mSelectLongPressTimeoutPreference;
197    private Preference mNoServicesMessagePreference;
198    private PreferenceScreen mCaptioningPreferenceScreen;
199    private PreferenceScreen mDisplayMagnificationPreferenceScreen;
200    private PreferenceScreen mGlobalGesturePreferenceScreen;
201
202    private int mLongPressTimeoutDefault;
203
204    @Override
205    public void onCreate(Bundle icicle) {
206        super.onCreate(icicle);
207        addPreferencesFromResource(R.xml.accessibility_settings);
208        initializeAllPreferences();
209    }
210
211    @Override
212    public void onResume() {
213        super.onResume();
214        loadInstalledServices();
215        updateAllPreferences();
216
217        offerInstallAccessibilitySerivceOnce();
218
219        mSettingsPackageMonitor.register(getActivity(), getActivity().getMainLooper(), false);
220        mSettingsContentObserver.register(getContentResolver());
221        if (RotationPolicy.isRotationSupported(getActivity())) {
222            RotationPolicy.registerRotationPolicyListener(getActivity(),
223                    mRotationPolicyListener);
224        }
225    }
226
227    @Override
228    public void onPause() {
229        mSettingsPackageMonitor.unregister();
230        mSettingsContentObserver.unregister(getContentResolver());
231        if (RotationPolicy.isRotationSupported(getActivity())) {
232            RotationPolicy.unregisterRotationPolicyListener(getActivity(),
233                    mRotationPolicyListener);
234        }
235        super.onPause();
236    }
237
238    @Override
239    public boolean onPreferenceChange(Preference preference, Object newValue) {
240        if (preference == mSelectLongPressTimeoutPreference) {
241            String stringValue = (String) newValue;
242            Settings.Secure.putInt(getContentResolver(),
243                    Settings.Secure.LONG_PRESS_TIMEOUT, Integer.parseInt(stringValue));
244            mSelectLongPressTimeoutPreference.setSummary(
245                    mLongPressTimeoutValuetoTitleMap.get(stringValue));
246            return true;
247        }
248        return false;
249    }
250
251    @Override
252    public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) {
253        if (mToggleLargeTextPreference == preference) {
254            handleToggleLargeTextPreferenceClick();
255            return true;
256        } else if (mTogglePowerButtonEndsCallPreference == preference) {
257            handleTogglePowerButtonEndsCallPreferenceClick();
258            return true;
259        } else if (mToggleLockScreenRotationPreference == preference) {
260            handleLockScreenRotationPreferenceClick();
261            return true;
262        } else if (mToggleSpeakPasswordPreference == preference) {
263            handleToggleSpeakPasswordPreferenceClick();
264            return true;
265        } else if (mGlobalGesturePreferenceScreen == preference) {
266            handleTogglEnableAccessibilityGesturePreferenceClick();
267            return true;
268        } else if (mDisplayMagnificationPreferenceScreen == preference) {
269            handleDisplayMagnificationPreferenceScreenClick();
270            return true;
271        }
272        return super.onPreferenceTreeClick(preferenceScreen, preference);
273    }
274
275    private void handleToggleLargeTextPreferenceClick() {
276        try {
277            mCurConfig.fontScale = mToggleLargeTextPreference.isChecked() ? LARGE_FONT_SCALE : 1;
278            ActivityManagerNative.getDefault().updatePersistentConfiguration(mCurConfig);
279        } catch (RemoteException re) {
280            /* ignore */
281        }
282    }
283
284    private void handleTogglePowerButtonEndsCallPreferenceClick() {
285        Settings.Secure.putInt(getContentResolver(),
286                Settings.Secure.INCALL_POWER_BUTTON_BEHAVIOR,
287                (mTogglePowerButtonEndsCallPreference.isChecked()
288                        ? Settings.Secure.INCALL_POWER_BUTTON_BEHAVIOR_HANGUP
289                        : Settings.Secure.INCALL_POWER_BUTTON_BEHAVIOR_SCREEN_OFF));
290    }
291
292    private void handleLockScreenRotationPreferenceClick() {
293        RotationPolicy.setRotationLockForAccessibility(getActivity(),
294                !mToggleLockScreenRotationPreference.isChecked());
295    }
296
297    private void handleToggleSpeakPasswordPreferenceClick() {
298        Settings.Secure.putInt(getContentResolver(),
299                Settings.Secure.ACCESSIBILITY_SPEAK_PASSWORD,
300                mToggleSpeakPasswordPreference.isChecked() ? 1 : 0);
301    }
302
303    private void handleTogglEnableAccessibilityGesturePreferenceClick() {
304        Bundle extras = mGlobalGesturePreferenceScreen.getExtras();
305        extras.putString(EXTRA_TITLE, getString(
306                R.string.accessibility_global_gesture_preference_title));
307        extras.putString(EXTRA_SUMMARY, getString(
308                R.string.accessibility_global_gesture_preference_description));
309        extras.putBoolean(EXTRA_CHECKED, Settings.Global.getInt(getContentResolver(),
310                Settings.Global.ENABLE_ACCESSIBILITY_GLOBAL_GESTURE_ENABLED, 0) == 1);
311        super.onPreferenceTreeClick(mGlobalGesturePreferenceScreen,
312                mGlobalGesturePreferenceScreen);
313    }
314
315    private void handleDisplayMagnificationPreferenceScreenClick() {
316        Bundle extras = mDisplayMagnificationPreferenceScreen.getExtras();
317        extras.putString(EXTRA_TITLE, getString(
318                R.string.accessibility_screen_magnification_title));
319        extras.putCharSequence(EXTRA_SUMMARY, getActivity().getResources().getText(
320                R.string.accessibility_screen_magnification_summary));
321        extras.putBoolean(EXTRA_CHECKED, Settings.Secure.getInt(getContentResolver(),
322                Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED, 0) == 1);
323        super.onPreferenceTreeClick(mDisplayMagnificationPreferenceScreen,
324                mDisplayMagnificationPreferenceScreen);
325    }
326
327    private void initializeAllPreferences() {
328        mServicesCategory = (PreferenceCategory) findPreference(SERVICES_CATEGORY);
329        mSystemsCategory = (PreferenceCategory) findPreference(SYSTEM_CATEGORY);
330
331        // Large text.
332        mToggleLargeTextPreference =
333                (CheckBoxPreference) findPreference(TOGGLE_LARGE_TEXT_PREFERENCE);
334
335        // Power button ends calls.
336        mTogglePowerButtonEndsCallPreference =
337                (CheckBoxPreference) findPreference(TOGGLE_POWER_BUTTON_ENDS_CALL_PREFERENCE);
338        if (!KeyCharacterMap.deviceHasKey(KeyEvent.KEYCODE_POWER)
339                || !Utils.isVoiceCapable(getActivity())) {
340            mSystemsCategory.removePreference(mTogglePowerButtonEndsCallPreference);
341        }
342
343        // Lock screen rotation.
344        mToggleLockScreenRotationPreference =
345                (CheckBoxPreference) findPreference(TOGGLE_LOCK_SCREEN_ROTATION_PREFERENCE);
346        if (!RotationPolicy.isRotationSupported(getActivity())) {
347            mSystemsCategory.removePreference(mToggleLockScreenRotationPreference);
348        }
349
350        // Speak passwords.
351        mToggleSpeakPasswordPreference =
352                (CheckBoxPreference) findPreference(TOGGLE_SPEAK_PASSWORD_PREFERENCE);
353
354        // Long press timeout.
355        mSelectLongPressTimeoutPreference =
356                (ListPreference) findPreference(SELECT_LONG_PRESS_TIMEOUT_PREFERENCE);
357        mSelectLongPressTimeoutPreference.setOnPreferenceChangeListener(this);
358        if (mLongPressTimeoutValuetoTitleMap.size() == 0) {
359            String[] timeoutValues = getResources().getStringArray(
360                    R.array.long_press_timeout_selector_values);
361            mLongPressTimeoutDefault = Integer.parseInt(timeoutValues[0]);
362            String[] timeoutTitles = getResources().getStringArray(
363                    R.array.long_press_timeout_selector_titles);
364            final int timeoutValueCount = timeoutValues.length;
365            for (int i = 0; i < timeoutValueCount; i++) {
366                mLongPressTimeoutValuetoTitleMap.put(timeoutValues[i], timeoutTitles[i]);
367            }
368        }
369
370        // Captioning.
371        mCaptioningPreferenceScreen = (PreferenceScreen) findPreference(
372                CAPTIONING_PREFERENCE_SCREEN);
373
374        // Display magnification.
375        mDisplayMagnificationPreferenceScreen = (PreferenceScreen) findPreference(
376                DISPLAY_MAGNIFICATION_PREFERENCE_SCREEN);
377
378        // Global gesture.
379        mGlobalGesturePreferenceScreen =
380                (PreferenceScreen) findPreference(ENABLE_ACCESSIBILITY_GESTURE_PREFERENCE_SCREEN);
381        final int longPressOnPowerBehavior = getActivity().getResources().getInteger(
382                com.android.internal.R.integer.config_longPressOnPowerBehavior);
383        final int LONG_PRESS_POWER_GLOBAL_ACTIONS = 1;
384        if (!KeyCharacterMap.deviceHasKey(KeyEvent.KEYCODE_POWER)
385                || longPressOnPowerBehavior != LONG_PRESS_POWER_GLOBAL_ACTIONS) {
386            // Remove accessibility shortcut if power key is not present
387            // nor long press power does not show global actions menu.
388            mSystemsCategory.removePreference(mGlobalGesturePreferenceScreen);
389        }
390    }
391
392    private void updateAllPreferences() {
393        updateServicesPreferences();
394        updateSystemPreferences();
395    }
396
397    private void updateServicesPreferences() {
398        // Since services category is auto generated we have to do a pass
399        // to generate it since services can come and go and then based on
400        // the global accessibility state to decided whether it is enabled.
401
402        // Generate.
403        mServicesCategory.removeAll();
404
405        AccessibilityManager accessibilityManager = AccessibilityManager.getInstance(getActivity());
406
407        List<AccessibilityServiceInfo> installedServices =
408                accessibilityManager.getInstalledAccessibilityServiceList();
409        Set<ComponentName> enabledServices = AccessibilityUtils.getEnabledServicesFromSettings(
410                getActivity());
411
412        final boolean accessibilityEnabled = Settings.Secure.getInt(getContentResolver(),
413                Settings.Secure.ACCESSIBILITY_ENABLED, 0) == 1;
414
415        for (int i = 0, count = installedServices.size(); i < count; ++i) {
416            AccessibilityServiceInfo info = installedServices.get(i);
417
418            PreferenceScreen preference = getPreferenceManager().createPreferenceScreen(
419                    getActivity());
420            String title = info.getResolveInfo().loadLabel(getPackageManager()).toString();
421
422            ServiceInfo serviceInfo = info.getResolveInfo().serviceInfo;
423            ComponentName componentName = new ComponentName(serviceInfo.packageName,
424                    serviceInfo.name);
425
426            preference.setKey(componentName.flattenToString());
427
428            preference.setTitle(title);
429            final boolean serviceEnabled = accessibilityEnabled
430                    && enabledServices.contains(componentName);
431            if (serviceEnabled) {
432                preference.setSummary(getString(R.string.accessibility_feature_state_on));
433            } else {
434                preference.setSummary(getString(R.string.accessibility_feature_state_off));
435            }
436
437            preference.setOrder(i);
438            preference.setFragment(ToggleAccessibilityServicePreferenceFragment.class.getName());
439            preference.setPersistent(true);
440
441            Bundle extras = preference.getExtras();
442            extras.putString(EXTRA_PREFERENCE_KEY, preference.getKey());
443            extras.putBoolean(EXTRA_CHECKED, serviceEnabled);
444            extras.putString(EXTRA_TITLE, title);
445
446            String description = info.loadDescription(getPackageManager());
447            if (TextUtils.isEmpty(description)) {
448                description = getString(R.string.accessibility_service_default_description);
449            }
450            extras.putString(EXTRA_SUMMARY, description);
451
452            String settingsClassName = info.getSettingsActivityName();
453            if (!TextUtils.isEmpty(settingsClassName)) {
454                extras.putString(EXTRA_SETTINGS_TITLE,
455                        getString(R.string.accessibility_menu_item_settings));
456                extras.putString(EXTRA_SETTINGS_COMPONENT_NAME,
457                        new ComponentName(info.getResolveInfo().serviceInfo.packageName,
458                                settingsClassName).flattenToString());
459            }
460
461            extras.putParcelable(EXTRA_COMPONENT_NAME, componentName);
462
463            mServicesCategory.addPreference(preference);
464        }
465
466        if (mServicesCategory.getPreferenceCount() == 0) {
467            if (mNoServicesMessagePreference == null) {
468                mNoServicesMessagePreference = new Preference(getActivity()) {
469                        @Override
470                    protected void onBindView(View view) {
471                        super.onBindView(view);
472                        TextView summaryView = (TextView) view.findViewById(R.id.summary);
473                        String title = getString(R.string.accessibility_no_services_installed);
474                        summaryView.setText(title);
475                    }
476                };
477                mNoServicesMessagePreference.setPersistent(false);
478                mNoServicesMessagePreference.setLayoutResource(
479                        R.layout.text_description_preference);
480                mNoServicesMessagePreference.setSelectable(false);
481            }
482            mServicesCategory.addPreference(mNoServicesMessagePreference);
483        }
484    }
485
486    private void updateSystemPreferences() {
487        // Large text.
488        try {
489            mCurConfig.updateFrom(ActivityManagerNative.getDefault().getConfiguration());
490        } catch (RemoteException re) {
491            /* ignore */
492        }
493        mToggleLargeTextPreference.setChecked(mCurConfig.fontScale == LARGE_FONT_SCALE);
494
495        // Power button ends calls.
496        if (KeyCharacterMap.deviceHasKey(KeyEvent.KEYCODE_POWER)
497                && Utils.isVoiceCapable(getActivity())) {
498            final int incallPowerBehavior = Settings.Secure.getInt(getContentResolver(),
499                    Settings.Secure.INCALL_POWER_BUTTON_BEHAVIOR,
500                    Settings.Secure.INCALL_POWER_BUTTON_BEHAVIOR_DEFAULT);
501            final boolean powerButtonEndsCall =
502                    (incallPowerBehavior == Settings.Secure.INCALL_POWER_BUTTON_BEHAVIOR_HANGUP);
503            mTogglePowerButtonEndsCallPreference.setChecked(powerButtonEndsCall);
504        }
505
506        // Auto-rotate screen
507        updateLockScreenRotationCheckbox();
508
509        // Speak passwords.
510        final boolean speakPasswordEnabled = Settings.Secure.getInt(getContentResolver(),
511                Settings.Secure.ACCESSIBILITY_SPEAK_PASSWORD, 0) != 0;
512        mToggleSpeakPasswordPreference.setChecked(speakPasswordEnabled);
513
514        // Long press timeout.
515        final int longPressTimeout = Settings.Secure.getInt(getContentResolver(),
516                Settings.Secure.LONG_PRESS_TIMEOUT, mLongPressTimeoutDefault);
517        String value = String.valueOf(longPressTimeout);
518        mSelectLongPressTimeoutPreference.setValue(value);
519        mSelectLongPressTimeoutPreference.setSummary(mLongPressTimeoutValuetoTitleMap.get(value));
520
521        // Captioning.
522        final boolean captioningEnabled = Settings.Secure.getInt(getContentResolver(),
523                Settings.Secure.ACCESSIBILITY_CAPTIONING_ENABLED, 0) == 1;
524        if (captioningEnabled) {
525            mCaptioningPreferenceScreen.setSummary(R.string.accessibility_feature_state_on);
526        } else {
527            mCaptioningPreferenceScreen.setSummary(R.string.accessibility_feature_state_off);
528        }
529
530        // Screen magnification.
531        final boolean magnificationEnabled = Settings.Secure.getInt(getContentResolver(),
532                Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED, 0) == 1;
533        if (magnificationEnabled) {
534            mDisplayMagnificationPreferenceScreen.setSummary(
535                    R.string.accessibility_feature_state_on);
536        } else {
537            mDisplayMagnificationPreferenceScreen.setSummary(
538                    R.string.accessibility_feature_state_off);
539        }
540
541        // Global gesture
542        final boolean globalGestureEnabled = Settings.Global.getInt(getContentResolver(),
543                Settings.Global.ENABLE_ACCESSIBILITY_GLOBAL_GESTURE_ENABLED, 0) == 1;
544        if (globalGestureEnabled) {
545            mGlobalGesturePreferenceScreen.setSummary(
546                    R.string.accessibility_global_gesture_preference_summary_on);
547        } else {
548            mGlobalGesturePreferenceScreen.setSummary(
549                    R.string.accessibility_global_gesture_preference_summary_off);
550        }
551    }
552
553    private void updateLockScreenRotationCheckbox() {
554        Context context = getActivity();
555        if (context != null) {
556            mToggleLockScreenRotationPreference.setChecked(
557                    !RotationPolicy.isRotationLocked(context));
558        }
559    }
560
561    private void offerInstallAccessibilitySerivceOnce() {
562        // There is always one preference - if no services it is just a message.
563        if (mServicesCategory.getPreference(0) != mNoServicesMessagePreference) {
564            return;
565        }
566        SharedPreferences preferences = getActivity().getPreferences(Context.MODE_PRIVATE);
567        final boolean offerInstallService = !preferences.getBoolean(
568                KEY_INSTALL_ACCESSIBILITY_SERVICE_OFFERED_ONCE, false);
569        if (offerInstallService) {
570            String screenreaderMarketLink = SystemProperties.get(
571                    SYSTEM_PROPERTY_MARKET_URL,
572                    DEFAULT_SCREENREADER_MARKET_LINK);
573            Uri marketUri = Uri.parse(screenreaderMarketLink);
574            Intent marketIntent = new Intent(Intent.ACTION_VIEW, marketUri);
575
576            if (getPackageManager().resolveActivity(marketIntent, 0) == null) {
577                // Don't show the dialog if no market app is found/installed.
578                return;
579            }
580
581            preferences.edit().putBoolean(KEY_INSTALL_ACCESSIBILITY_SERVICE_OFFERED_ONCE,
582                    true).commit();
583            // Notify user that they do not have any accessibility
584            // services installed and direct them to Market to get TalkBack.
585            showDialog(DIALOG_ID_NO_ACCESSIBILITY_SERVICES);
586        }
587    }
588
589    @Override
590    public Dialog onCreateDialog(int dialogId) {
591        switch (dialogId) {
592            case DIALOG_ID_NO_ACCESSIBILITY_SERVICES:
593                return new AlertDialog.Builder(getActivity())
594                .setTitle(R.string.accessibility_service_no_apps_title)
595                        .setMessage(R.string.accessibility_service_no_apps_message)
596                        .setPositiveButton(android.R.string.ok,
597                                new DialogInterface.OnClickListener() {
598                                    @Override
599                                    public void onClick(DialogInterface dialog, int which) {
600                                        // dismiss the dialog before launching
601                                        // the activity otherwise the dialog
602                                        // removal occurs after
603                                        // onSaveInstanceState which triggers an
604                                        // exception
605                                        removeDialog(DIALOG_ID_NO_ACCESSIBILITY_SERVICES);
606                                        String screenreaderMarketLink = SystemProperties.get(
607                                                SYSTEM_PROPERTY_MARKET_URL,
608                                                DEFAULT_SCREENREADER_MARKET_LINK);
609                                        Uri marketUri = Uri.parse(screenreaderMarketLink);
610                                        Intent marketIntent = new Intent(Intent.ACTION_VIEW,
611                                                marketUri);
612                                        try {
613                                            startActivity(marketIntent);
614                                        } catch (ActivityNotFoundException anfe) {
615                                            Log.w(LOG_TAG, "Couldn't start play store activity",
616                                                    anfe);
617                                        }
618                                    }
619                                })
620                        .setNegativeButton(android.R.string.cancel, null)
621                        .create();
622            default:
623                return null;
624        }
625    }
626
627    private void loadInstalledServices() {
628        Set<ComponentName> installedServices = sInstalledServices;
629        installedServices.clear();
630
631        List<AccessibilityServiceInfo> installedServiceInfos =
632                AccessibilityManager.getInstance(getActivity())
633                        .getInstalledAccessibilityServiceList();
634        if (installedServiceInfos == null) {
635            return;
636        }
637
638        final int installedServiceInfoCount = installedServiceInfos.size();
639        for (int i = 0; i < installedServiceInfoCount; i++) {
640            ResolveInfo resolveInfo = installedServiceInfos.get(i).getResolveInfo();
641            ComponentName installedService = new ComponentName(
642                    resolveInfo.serviceInfo.packageName,
643                    resolveInfo.serviceInfo.name);
644            installedServices.add(installedService);
645        }
646    }
647}
648