OSDN Git Service

Refactor IMEAndLanguageSettings to use PreferenceController
authorFan Zhang <zhfan@google.com>
Thu, 3 Nov 2016 20:29:06 +0000 (13:29 -0700)
committerFan Zhang <zhfan@google.com>
Mon, 7 Nov 2016 23:29:04 +0000 (15:29 -0800)
Bug: 32642706
Bug: 32637613
Bug: 32643833
Test: RunSettingsRoboTests

This change is necessary to test preference controlling logic, as well
as making preferences reusable in other Preference pages in the new
information architecture.

Change-Id: I9e2fd2f6800579f005a5c303ed03ae06477006a6

14 files changed:
res/xml/language_settings.xml
src/com/android/settings/VoiceInputOutputSettings.java [deleted file]
src/com/android/settings/inputmethod/GameControllerPreferenceController.java [new file with mode: 0644]
src/com/android/settings/inputmethod/InputMethodAndLanguageSettings.java
src/com/android/settings/inputmethod/SpellCheckerPreferenceController.java [new file with mode: 0644]
src/com/android/settings/language/PhoneLanguagePreferenceController.java [new file with mode: 0644]
src/com/android/settings/language/TtsPreferenceController.java [new file with mode: 0644]
src/com/android/settings/language/UserDictionaryPreferenceController.java [new file with mode: 0644]
tests/robotests/src/com/android/settings/gestures/SwipeToNotificationPreferenceControllerTest.java
tests/robotests/src/com/android/settings/inputmethod/GameControllerPreferenceControllerTest.java [new file with mode: 0644]
tests/robotests/src/com/android/settings/inputmethod/SpellCheckerPreferenceControllerTest.java [new file with mode: 0644]
tests/robotests/src/com/android/settings/language/PhoneLanguagePreferenceControllerTest.java [new file with mode: 0644]
tests/robotests/src/com/android/settings/language/TtsPreferenceControllerTest.java [new file with mode: 0644]
tests/robotests/src/com/android/settings/language/UserDictionaryPreferenceControllerTest.java [new file with mode: 0644]

index d829dc4..992af92 100644 (file)
     <!-- Spell checker preference title, summary and fragment will be set programmatically. -->
     <!-- Note: Mark this as persistent="false" to remove unnecessarily saved shared preference.
          See: InputMethodAndSubtypeUtil.removeUnnecessaryNonPersistentPreference. -->
-    <PreferenceScreen
-            android:key="spellcheckers_settings"
-            android:title="@string/spellcheckers_settings_title"
-            android:persistent="false"
-            />
+    <Preference
+        android:key="spellcheckers_settings"
+        android:title="@string/spellcheckers_settings_title"
+        android:fragment="com.android.settings.inputmethod.SpellCheckersSettings"
+        android:persistent="false"/>
 
     <!-- User dictionary preference title and fragment will be set programmatically. -->
     <PreferenceScreen
diff --git a/src/com/android/settings/VoiceInputOutputSettings.java b/src/com/android/settings/VoiceInputOutputSettings.java
deleted file mode 100644 (file)
index 3a8f1a5..0000000
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.settings;
-
-import android.speech.tts.TtsEngines;
-import android.support.v7.preference.Preference;
-import android.support.v7.preference.PreferenceCategory;
-import android.support.v7.preference.PreferenceGroup;
-
-/**
- * Settings screen for voice input/output.
- */
-public class VoiceInputOutputSettings {
-
-    private static final String TAG = "VoiceInputOutputSettings";
-
-    private static final String KEY_VOICE_CATEGORY = "voice_category";
-    private static final String KEY_TTS_SETTINGS = "tts_settings";
-
-    private PreferenceGroup mParent;
-    private PreferenceCategory mVoiceCategory;
-    private Preference mVoiceInputSettingsPref;
-    private Preference mTtsSettingsPref;
-    private final SettingsPreferenceFragment mFragment;
-    private final TtsEngines mTtsEngines;
-
-    public VoiceInputOutputSettings(SettingsPreferenceFragment fragment) {
-        mFragment = fragment;
-        mTtsEngines = new TtsEngines(fragment.getPreferenceScreen().getContext());
-    }
-
-    public void onCreate() {
-        mParent = mFragment.getPreferenceScreen();
-        mVoiceCategory = (PreferenceCategory) mParent.findPreference(KEY_VOICE_CATEGORY);
-        mTtsSettingsPref = mVoiceCategory.findPreference(KEY_TTS_SETTINGS);
-
-        populateOrRemovePreferences();
-    }
-
-    private void populateOrRemovePreferences() {
-        boolean hasTtsPrefs = populateOrRemoveTtsPrefs();
-        if (!hasTtsPrefs) {
-            // There were no TTS settings and no recognizer settings,
-            // so it should be safe to hide the preference category
-            // entirely.
-            mFragment.getPreferenceScreen().removePreference(mVoiceCategory);
-        }
-    }
-
-    private boolean populateOrRemoveTtsPrefs() {
-        if (mTtsEngines.getEngines().isEmpty()) {
-            mVoiceCategory.removePreference(mTtsSettingsPref);
-            return false;
-        }
-
-        return true;
-    }
-}
diff --git a/src/com/android/settings/inputmethod/GameControllerPreferenceController.java b/src/com/android/settings/inputmethod/GameControllerPreferenceController.java
new file mode 100644 (file)
index 0000000..f8232b8
--- /dev/null
@@ -0,0 +1,131 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.inputmethod;
+
+import android.content.Context;
+import android.hardware.input.InputManager;
+import android.provider.Settings;
+import android.support.v14.preference.SwitchPreference;
+import android.support.v7.preference.Preference;
+import android.support.v7.preference.PreferenceScreen;
+import android.text.TextUtils;
+import android.view.InputDevice;
+
+import com.android.settings.core.PreferenceController;
+import com.android.settings.core.lifecycle.LifecycleObserver;
+import com.android.settings.core.lifecycle.events.OnPause;
+import com.android.settings.core.lifecycle.events.OnResume;
+
+public class GameControllerPreferenceController extends PreferenceController
+        implements InputManager.InputDeviceListener, LifecycleObserver, OnResume, OnPause {
+
+    public static final String PREF_KEY = "vibrate_input_devices";
+    private static final String CATEGORY_KEY = "game_controller_settings_category";
+
+    private final InputManager mIm;
+
+    private PreferenceScreen mScreen;
+    private Preference mCategory;
+    private Preference mPreference;
+
+    public GameControllerPreferenceController(Context context) {
+        super(context);
+        mIm = (InputManager) context.getSystemService(Context.INPUT_SERVICE);
+    }
+
+    @Override
+    public void onResume() {
+        mIm.registerInputDeviceListener(this, null);
+    }
+
+    @Override
+    public void onPause() {
+        mIm.unregisterInputDeviceListener(this);
+    }
+
+    @Override
+    public void displayPreference(PreferenceScreen screen) {
+        super.displayPreference(screen);
+        mScreen = screen;
+        mCategory = screen.findPreference(CATEGORY_KEY);
+        mPreference = screen.findPreference(PREF_KEY);
+    }
+
+    @Override
+    public boolean isAvailable() {
+        final int[] devices = mIm.getInputDeviceIds();
+        for (int deviceId : devices) {
+            InputDevice device = mIm.getInputDevice(deviceId);
+            if (device != null && !device.isVirtual() && device.getVibrator().hasVibrator()) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    @Override
+    public boolean handlePreferenceTreeClick(Preference preference) {
+        if (TextUtils.equals(PREF_KEY, preference.getKey())) {
+            Settings.System.putInt(mContext.getContentResolver(),
+                    Settings.System.VIBRATE_INPUT_DEVICES,
+                    ((SwitchPreference) preference).isChecked() ? 1 : 0);
+            return true;
+        }
+        return false;
+    }
+
+    @Override
+    public String getPreferenceKey() {
+        return CATEGORY_KEY;
+    }
+
+    @Override
+    public void updateState(Preference preference) {
+        if (preference == null) {
+            return;
+        }
+        ((SwitchPreference) preference).setChecked(Settings.System.getInt(
+                mContext.getContentResolver(),
+                Settings.System.VIBRATE_INPUT_DEVICES, 1) > 0);
+    }
+
+    @Override
+    public void onInputDeviceAdded(int deviceId) {
+        updateGameControllers();
+    }
+
+    @Override
+    public void onInputDeviceRemoved(int deviceId) {
+        updateGameControllers();
+    }
+
+    @Override
+    public void onInputDeviceChanged(int deviceId) {
+        updateGameControllers();
+    }
+
+    private void updateGameControllers() {
+        if (isAvailable()) {
+            mScreen.addPreference(mCategory);
+            updateState(mPreference);
+        } else {
+            if (mCategory != null) {
+                mScreen.removePreference(mCategory);
+            }
+        }
+    }
+}
index 9fa2645..351f6b5 100644 (file)
@@ -17,7 +17,6 @@
 package com.android.settings.inputmethod;
 
 import android.app.Activity;
-import android.app.Fragment;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
@@ -25,52 +24,38 @@ import android.content.pm.ServiceInfo;
 import android.hardware.input.InputDeviceIdentifier;
 import android.hardware.input.InputManager;
 import android.hardware.input.KeyboardLayout;
-import android.os.Bundle;
-import android.provider.Settings;
-import android.provider.Settings.System;
 import android.speech.tts.TtsEngines;
-import android.support.v14.preference.SwitchPreference;
 import android.support.v7.preference.Preference;
-import android.support.v7.preference.Preference.OnPreferenceClickListener;
-import android.support.v7.preference.PreferenceCategory;
 import android.view.InputDevice;
 import android.view.inputmethod.InputMethodInfo;
 import android.view.inputmethod.InputMethodManager;
 import android.view.inputmethod.InputMethodSubtype;
-import android.view.textservice.SpellCheckerInfo;
-import android.view.textservice.TextServicesManager;
 
 import com.android.internal.logging.MetricsProto.MetricsEvent;
 import com.android.settings.R;
 import com.android.settings.Settings.KeyboardLayoutPickerActivity;
-import com.android.settings.SettingsActivity;
-import com.android.settings.SettingsPreferenceFragment;
-import com.android.settings.SubSettings;
-import com.android.settings.UserDictionarySettings;
 import com.android.settings.Utils;
-import com.android.settings.VoiceInputOutputSettings;
+import com.android.settings.core.PreferenceController;
+import com.android.settings.dashboard.DashboardFragment;
 import com.android.settings.dashboard.SummaryLoader;
+import com.android.settings.language.PhoneLanguagePreferenceController;
+import com.android.settings.language.TtsPreferenceController;
+import com.android.settings.language.UserDictionaryPreferenceController;
 import com.android.settings.localepicker.LocaleFeatureProvider;
 import com.android.settings.overlay.FeatureFactory;
 import com.android.settings.search.BaseSearchIndexProvider;
 import com.android.settings.search.Indexable;
 import com.android.settings.search.SearchIndexableRaw;
+import com.android.settingslib.drawer.CategoryKey;
 
 import java.util.ArrayList;
 import java.util.List;
-import java.util.TreeSet;
 
-public class InputMethodAndLanguageSettings extends SettingsPreferenceFragment
-        implements InputManager.InputDeviceListener,
-        KeyboardLayoutDialogFragment.OnSetupKeyboardLayoutsListener, Indexable {
+public class InputMethodAndLanguageSettings extends DashboardFragment
+        implements KeyboardLayoutDialogFragment.OnSetupKeyboardLayoutsListener, Indexable {
 
-    private static final String KEY_SPELL_CHECKERS = "spellcheckers_settings";
-    private static final String KEY_PHONE_LANGUAGE = "phone_language";
-    private static final String KEY_USER_DICTIONARY_SETTINGS = "key_user_dictionary_settings";
+    private static final String TAG = "IMEAndLanguageSetting";
 
-    private PreferenceCategory mGameControllerCategory;
-    private Preference mLanguagePref;
-    private InputManager mIm;
     private Intent mIntentWaitingForResult;
 
     @Override
@@ -79,137 +64,33 @@ public class InputMethodAndLanguageSettings extends SettingsPreferenceFragment
     }
 
     @Override
-    public void onCreate(Bundle icicle) {
-        super.onCreate(icicle);
-
-        addPreferencesFromResource(R.xml.language_settings);
-
-        final Activity activity = getActivity();
-
-        if (activity.getAssets().getLocales().length == 1) {
-            // No "Select language" pref if there's only one system locale available.
-            getPreferenceScreen().removePreference(findPreference(KEY_PHONE_LANGUAGE));
-        } else {
-            mLanguagePref = findPreference(KEY_PHONE_LANGUAGE);
-        }
-
-        new VoiceInputOutputSettings(this).onCreate();
-
-        mGameControllerCategory = (PreferenceCategory)findPreference(
-                "game_controller_settings_category");
-
-        // Build hard keyboard and game controller preference categories.
-        mIm = (InputManager)activity.getSystemService(Context.INPUT_SERVICE);
-        updateInputDevices();
-
-        // Spell Checker
-        final Preference spellChecker = findPreference(KEY_SPELL_CHECKERS);
-        if (spellChecker != null) {
-            // Note: KEY_SPELL_CHECKERS preference is marked as persistent="false" in XML.
-            InputMethodAndSubtypeUtil.removeUnnecessaryNonPersistentPreference(spellChecker);
-            final Intent intent = new Intent(Intent.ACTION_MAIN);
-            intent.setClass(activity, SubSettings.class);
-            intent.putExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT,
-                    SpellCheckersSettings.class.getName());
-            intent.putExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT_TITLE_RESID,
-                    R.string.spellcheckers_settings_title);
-            spellChecker.setIntent(intent);
-        }
-    }
-
-    private void updateUserDictionaryPreference(Preference userDictionaryPreference) {
-        final Activity activity = getActivity();
-        final TreeSet<String> localeSet = UserDictionaryList.getUserDictionaryLocalesSet(activity);
-        if (null == localeSet) {
-            // The locale list is null if and only if the user dictionary service is
-            // not present or disabled. In this case we need to remove the preference.
-            getPreferenceScreen().removePreference(userDictionaryPreference);
-        } else {
-            userDictionaryPreference.setOnPreferenceClickListener(
-                    new OnPreferenceClickListener() {
-                        @Override
-                        public boolean onPreferenceClick(Preference arg0) {
-                            // Redirect to UserDictionarySettings if the user needs only one
-                            // language.
-                            final Bundle extras = new Bundle();
-                            final Class<? extends Fragment> targetFragment;
-                            if (localeSet.size() <= 1) {
-                                if (!localeSet.isEmpty()) {
-                                    // If the size of localeList is 0, we don't set the locale
-                                    // parameter in the extras. This will be interpreted by the
-                                    // UserDictionarySettings class as meaning
-                                    // "the current locale". Note that with the current code for
-                                    // UserDictionaryList#getUserDictionaryLocalesSet()
-                                    // the locale list always has at least one element, since it
-                                    // always includes the current locale explicitly.
-                                    // @see UserDictionaryList.getUserDictionaryLocalesSet().
-                                    extras.putString("locale", localeSet.first());
-                                }
-                                targetFragment = UserDictionarySettings.class;
-                            } else {
-                                targetFragment = UserDictionaryList.class;
-                            }
-                            startFragment(InputMethodAndLanguageSettings.this,
-                                    targetFragment.getCanonicalName(), -1, -1, extras);
-                            return true;
-                        }
-                    });
-        }
-    }
-
-    @Override
-    public void onResume() {
-        super.onResume();
-
-        mIm.registerInputDeviceListener(this, null);
-
-        final Preference spellChecker = findPreference(KEY_SPELL_CHECKERS);
-        if (spellChecker != null) {
-            final TextServicesManager tsm = (TextServicesManager) getSystemService(
-                    Context.TEXT_SERVICES_MANAGER_SERVICE);
-            if (!tsm.isSpellCheckerEnabled()) {
-                spellChecker.setSummary(R.string.switch_off_text);
-            } else {
-                final SpellCheckerInfo sci = tsm.getCurrentSpellChecker();
-                if (sci != null) {
-                    spellChecker.setSummary(sci.loadLabel(getPackageManager()));
-                } else {
-                    spellChecker.setSummary(R.string.spell_checker_not_selected);
-                }
-            }
-        }
-
-        if (mLanguagePref != null) {
-            final String localeNames = FeatureFactory.getFactory(getContext())
-                    .getLocaleFeatureProvider().getLocaleNames();
-            mLanguagePref.setSummary(localeNames);
-        }
-
-        updateUserDictionaryPreference(findPreference(KEY_USER_DICTIONARY_SETTINGS));
-
-        updateInputDevices();
+    protected String getCategoryKey() {
+        return CategoryKey.CATEGORY_SYSTEM_LANGUAGE;
     }
 
     @Override
-    public void onPause() {
-        super.onPause();
-
-        mIm.unregisterInputDeviceListener(this);
+    protected String getLogTag() {
+        return TAG;
     }
 
     @Override
-    public void onInputDeviceAdded(int deviceId) {
-        updateInputDevices();
+    protected int getPreferenceScreenResId() {
+        return R.xml.language_settings;
     }
 
     @Override
-    public void onInputDeviceChanged(int deviceId) {
-        updateInputDevices();
-    }
-
-    @Override
-    public void onInputDeviceRemoved(int deviceId) {
-        updateInputDevices();
+    protected List<PreferenceController> getPreferenceControllers(Context context) {
+        final GameControllerPreferenceController gameControllerPreferenceController =
+                new GameControllerPreferenceController(context);
+        getLifecycle().addObserver(gameControllerPreferenceController);
+
+        final List<PreferenceController> list = new ArrayList<>();
+        list.add(gameControllerPreferenceController);
+        list.add(new PhoneLanguagePreferenceController(context));
+        list.add(new SpellCheckerPreferenceController(context));
+        list.add(new UserDictionaryPreferenceController(context));
+        list.add(new TtsPreferenceController(context, new TtsEngines(context)));
+        return list;
     }
 
     @Override
@@ -218,21 +99,9 @@ public class InputMethodAndLanguageSettings extends SettingsPreferenceFragment
         if (Utils.isMonkeyRunning()) {
             return false;
         }
-        if (preference instanceof SwitchPreference) {
-            final SwitchPreference pref = (SwitchPreference) preference;
-            if (pref == mGameControllerCategory.findPreference("vibrate_input_devices")) {
-                System.putInt(getContentResolver(), Settings.System.VIBRATE_INPUT_DEVICES,
-                        pref.isChecked() ? 1 : 0);
-                return true;
-            }
-        }
         return super.onPreferenceTreeClick(preference);
     }
 
-    private void updateInputDevices() {
-        updateGameControllers();
-    }
-
     private void showKeyboardLayoutDialog(InputDeviceIdentifier inputDeviceIdentifier) {
         KeyboardLayoutDialogFragment fragment = (KeyboardLayoutDialogFragment)
                 getFragmentManager().findFragmentByTag("keyboardLayout");
@@ -265,38 +134,12 @@ public class InputMethodAndLanguageSettings extends SettingsPreferenceFragment
         }
     }
 
-    private void updateGameControllers() {
-        if (haveInputDeviceWithVibrator()) {
-            getPreferenceScreen().addPreference(mGameControllerCategory);
-
-            SwitchPreference pref = (SwitchPreference)
-                    mGameControllerCategory.findPreference("vibrate_input_devices");
-            pref.setChecked(System.getInt(getContentResolver(),
-                    Settings.System.VIBRATE_INPUT_DEVICES, 1) > 0);
-        } else {
-            getPreferenceScreen().removePreference(mGameControllerCategory);
-        }
-    }
-
-    private static boolean haveInputDeviceWithVibrator() {
-        final int[] devices = InputDevice.getDeviceIds();
-        for (int i = 0; i < devices.length; i++) {
-            InputDevice device = InputDevice.getDevice(devices[i]);
-            if (device != null && !device.isVirtual() && device.getVibrator().hasVibrator()) {
-                return true;
-            }
-        }
-        return false;
-    }
-
     private static class SummaryProvider implements SummaryLoader.SummaryProvider {
 
-        private final Context mContext;
         private final SummaryLoader mSummaryLoader;
         private LocaleFeatureProvider mLocaleFeatureProvider;
 
         public SummaryProvider(Context context, SummaryLoader summaryLoader) {
-            mContext = context;
             mSummaryLoader = summaryLoader;
             mLocaleFeatureProvider = FeatureFactory.getFactory(context).getLocaleFeatureProvider();
         }
@@ -328,11 +171,13 @@ public class InputMethodAndLanguageSettings extends SettingsPreferenceFragment
             final String screenTitle = context.getString(R.string.language_keyboard_settings_title);
 
             // Locale picker.
-            if (context.getAssets().getLocales().length > 1) {
+            final PhoneLanguagePreferenceController mLanguagePrefController =
+                    new PhoneLanguagePreferenceController(context);
+            if (mLanguagePrefController.isAvailable()) {
                 String localeNames = FeatureFactory.getFactory(context).getLocaleFeatureProvider()
                         .getLocaleNames();
                 SearchIndexableRaw indexable = new SearchIndexableRaw(context);
-                indexable.key = KEY_PHONE_LANGUAGE;
+                indexable.key = mLanguagePrefController.getPreferenceKey();
                 indexable.title = context.getString(R.string.phone_language);
                 indexable.summaryOn = localeNames;
                 indexable.summaryOff = localeNames;
@@ -342,7 +187,7 @@ public class InputMethodAndLanguageSettings extends SettingsPreferenceFragment
 
             // Spell checker.
             SearchIndexableRaw indexable = new SearchIndexableRaw(context);
-            indexable.key = KEY_SPELL_CHECKERS;
+            indexable.key = SpellCheckerPreferenceController.KEY_SPELL_CHECKERS;
             indexable.title = context.getString(R.string.spellcheckers_settings_title);
             indexable.screenTitle = screenTitle;
             indexable.keywords = context.getString(R.string.keywords_spell_checker);
@@ -465,9 +310,9 @@ public class InputMethodAndLanguageSettings extends SettingsPreferenceFragment
             indexables.add(indexable);
 
             // Game controllers.
-            if (haveInputDeviceWithVibrator()) {
+            if (!new GameControllerPreferenceController(context).isAvailable()) {
                 indexable = new SearchIndexableRaw(context);
-                indexable.key = "vibrate_input_devices";
+                indexable.key = GameControllerPreferenceController.PREF_KEY;
                 indexable.title = context.getString(R.string.vibrate_input_devices);
                 indexable.summaryOn = context.getString(R.string.vibrate_input_devices_summary);
                 indexable.summaryOff = context.getString(R.string.vibrate_input_devices_summary);
diff --git a/src/com/android/settings/inputmethod/SpellCheckerPreferenceController.java b/src/com/android/settings/inputmethod/SpellCheckerPreferenceController.java
new file mode 100644 (file)
index 0000000..17d3fdc
--- /dev/null
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.inputmethod;
+
+import android.content.Context;
+import android.support.v7.preference.Preference;
+import android.support.v7.preference.PreferenceScreen;
+import android.view.textservice.SpellCheckerInfo;
+import android.view.textservice.TextServicesManager;
+
+import com.android.settings.R;
+import com.android.settings.core.PreferenceController;
+
+public class SpellCheckerPreferenceController extends PreferenceController {
+
+    public static final String KEY_SPELL_CHECKERS = "spellcheckers_settings";
+
+    private final TextServicesManager mTextServicesManager;
+
+    public SpellCheckerPreferenceController(Context context) {
+        super(context);
+        mTextServicesManager = (TextServicesManager) context.getSystemService(
+                Context.TEXT_SERVICES_MANAGER_SERVICE);
+    }
+
+    @Override
+    public void displayPreference(PreferenceScreen screen) {
+        super.displayPreference(screen);
+        final Preference preference = screen.findPreference(KEY_SPELL_CHECKERS);
+        if (preference != null) {
+            InputMethodAndSubtypeUtil.removeUnnecessaryNonPersistentPreference(preference);
+        }
+    }
+
+    @Override
+    public boolean isAvailable() {
+        return true;
+    }
+
+    @Override
+    public boolean handlePreferenceTreeClick(Preference preference) {
+        return false;
+    }
+
+    @Override
+    public String getPreferenceKey() {
+        return KEY_SPELL_CHECKERS;
+    }
+
+    @Override
+    public void updateState(Preference preference) {
+        if (preference == null) {
+            return;
+        }
+        if (!mTextServicesManager.isSpellCheckerEnabled()) {
+            preference.setSummary(R.string.switch_off_text);
+        } else {
+            final SpellCheckerInfo sci = mTextServicesManager.getCurrentSpellChecker();
+            if (sci != null) {
+                preference.setSummary(sci.loadLabel(mContext.getPackageManager()));
+            } else {
+                preference.setSummary(R.string.spell_checker_not_selected);
+            }
+        }
+    }
+}
diff --git a/src/com/android/settings/language/PhoneLanguagePreferenceController.java b/src/com/android/settings/language/PhoneLanguagePreferenceController.java
new file mode 100644 (file)
index 0000000..2b87fd9
--- /dev/null
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.language;
+
+import android.content.Context;
+import android.support.v7.preference.Preference;
+
+import com.android.settings.core.PreferenceController;
+import com.android.settings.overlay.FeatureFactory;
+
+public class PhoneLanguagePreferenceController extends PreferenceController {
+
+    private static final String KEY_PHONE_LANGUAGE = "phone_language";
+
+    public PhoneLanguagePreferenceController(Context context) {
+        super(context);
+    }
+
+    @Override
+    public boolean isAvailable() {
+        return mContext.getAssets().getLocales().length > 1;
+    }
+
+    @Override
+    public boolean handlePreferenceTreeClick(Preference preference) {
+        return false;
+    }
+
+    @Override
+    public void updateState(Preference preference) {
+        if (preference == null) {
+            return;
+        }
+        final String localeNames = FeatureFactory.getFactory(mContext)
+                .getLocaleFeatureProvider().getLocaleNames();
+        preference.setSummary(localeNames);
+    }
+
+    @Override
+    public String getPreferenceKey() {
+        return KEY_PHONE_LANGUAGE;
+    }
+}
diff --git a/src/com/android/settings/language/TtsPreferenceController.java b/src/com/android/settings/language/TtsPreferenceController.java
new file mode 100644 (file)
index 0000000..ffc1eb1
--- /dev/null
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.language;
+
+import android.content.Context;
+import android.speech.tts.TtsEngines;
+import android.support.v7.preference.Preference;
+import android.support.v7.preference.PreferenceScreen;
+
+import com.android.settings.core.PreferenceController;
+
+public class TtsPreferenceController extends PreferenceController {
+
+    private static final String KEY_VOICE_CATEGORY = "voice_category";
+    private static final String KEY_TTS_SETTINGS = "tts_settings";
+
+    private final TtsEngines mTtsEngines;
+
+    public TtsPreferenceController(Context context, TtsEngines ttsEngines) {
+        super(context);
+        mTtsEngines = ttsEngines;
+    }
+
+    @Override
+    public boolean isAvailable() {
+        return !mTtsEngines.getEngines().isEmpty();
+    }
+
+    @Override
+    public void displayPreference(PreferenceScreen screen) {
+        super.displayPreference(screen);
+        if (!isAvailable()) {
+            removePreference(screen, KEY_VOICE_CATEGORY);
+        }
+    }
+
+    @Override
+    public boolean handlePreferenceTreeClick(Preference preference) {
+        return false;
+    }
+
+    @Override
+    public String getPreferenceKey() {
+        return KEY_TTS_SETTINGS;
+    }
+}
diff --git a/src/com/android/settings/language/UserDictionaryPreferenceController.java b/src/com/android/settings/language/UserDictionaryPreferenceController.java
new file mode 100644 (file)
index 0000000..d26761b
--- /dev/null
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.language;
+
+import android.app.Fragment;
+import android.content.Context;
+import android.os.Bundle;
+import android.support.v7.preference.Preference;
+
+import com.android.settings.UserDictionarySettings;
+import com.android.settings.core.PreferenceController;
+import com.android.settings.inputmethod.UserDictionaryList;
+
+import java.util.TreeSet;
+
+public class UserDictionaryPreferenceController extends PreferenceController {
+
+    private static final String KEY_USER_DICTIONARY_SETTINGS = "key_user_dictionary_settings";
+
+    public UserDictionaryPreferenceController(Context context) {
+        super(context);
+    }
+
+    @Override
+    public boolean isAvailable() {
+        final TreeSet<String> localeSet = getDictionaryLocales();
+        // The locale list is null if and only if the user dictionary service is
+        // not present or disabled. In this case we need to remove the preference.
+        return localeSet != null;
+    }
+
+    @Override
+    public boolean handlePreferenceTreeClick(Preference preference) {
+        return false;
+    }
+
+    @Override
+    public String getPreferenceKey() {
+        return KEY_USER_DICTIONARY_SETTINGS;
+    }
+
+    @Override
+    public void updateState(Preference preference) {
+        if (!isAvailable() || preference == null) {
+            return;
+        }
+        final TreeSet<String> localeSet = getDictionaryLocales();
+        final Bundle extras = preference.getExtras();
+        final Class<? extends Fragment> targetFragment;
+        if (localeSet.size() <= 1) {
+            if (!localeSet.isEmpty()) {
+                // If the size of localeList is 0, we don't set the locale
+                // parameter in the extras. This will be interpreted by the
+                // UserDictionarySettings class as meaning
+                // "the current locale". Note that with the current code for
+                // UserDictionaryList#getUserDictionaryLocalesSet()
+                // the locale list always has at least one element, since it
+                // always includes the current locale explicitly.
+                // @see UserDictionaryList.getUserDictionaryLocalesSet().
+                extras.putString("locale", localeSet.first());
+            }
+            targetFragment = UserDictionarySettings.class;
+        } else {
+            targetFragment = UserDictionaryList.class;
+        }
+        preference.setFragment(targetFragment.getCanonicalName());
+    }
+
+    protected TreeSet<String> getDictionaryLocales() {
+        return UserDictionaryList.getUserDictionaryLocalesSet(mContext);
+    }
+}
index e2b4473..578b853 100644 (file)
@@ -22,6 +22,7 @@ import android.support.v7.preference.Preference;
 import android.support.v7.preference.PreferenceScreen;
 import android.support.v7.preference.TwoStatePreference;
 
+import com.android.settings.SettingsRobolectricTestRunner;
 import com.android.settings.TestConfig;
 
 import org.junit.Before;
@@ -30,7 +31,6 @@ import org.junit.runner.RunWith;
 import org.mockito.Answers;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
-import org.robolectric.RobolectricTestRunner;
 import org.robolectric.annotation.Config;
 import org.robolectric.shadows.ShadowApplication;
 
@@ -41,7 +41,7 @@ import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
-@RunWith(RobolectricTestRunner.class)
+@RunWith(SettingsRobolectricTestRunner.class)
 @Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
 public class SwipeToNotificationPreferenceControllerTest {
 
diff --git a/tests/robotests/src/com/android/settings/inputmethod/GameControllerPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/inputmethod/GameControllerPreferenceControllerTest.java
new file mode 100644 (file)
index 0000000..c1bd0e0
--- /dev/null
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.inputmethod;
+
+import android.content.Context;
+import android.hardware.input.InputManager;
+import android.view.InputDevice;
+
+import com.android.settings.SettingsRobolectricTestRunner;
+import com.android.settings.TestConfig;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Answers;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.annotation.Config;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class GameControllerPreferenceControllerTest {
+
+    @Mock(answer = Answers.RETURNS_DEEP_STUBS)
+    private Context mContext;
+    @Mock
+    private InputManager mInputManager;
+    @Mock(answer = Answers.RETURNS_DEEP_STUBS)
+    private InputDevice mInputDevice;
+
+    private GameControllerPreferenceController mController;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        when(mContext.getSystemService(Context.INPUT_SERVICE)).thenReturn(mInputManager);
+        mController = new GameControllerPreferenceController(mContext);
+    }
+
+    @Test
+    public void testLifecycle_shouldRegisterInputManager() {
+        mController.onResume();
+
+        // register is called, but unregister should not be called.
+        verify(mInputManager).registerInputDeviceListener(mController, null);
+        verify(mInputManager, never()).unregisterInputDeviceListener(mController);
+
+        mController.onPause();
+        // register is not called any more times, but unregister should be called once.
+        verify(mInputManager).registerInputDeviceListener(mController, null);
+        verify(mInputManager).unregisterInputDeviceListener(mController);
+    }
+
+    @Test
+    public void testIsAvailable_hasDeviceWithVibrator_shouldReturnTrue() {
+        when(mInputManager.getInputDeviceIds()).thenReturn(new int[]{1});
+        when(mInputManager.getInputDevice(1)).thenReturn(mInputDevice);
+        when(mInputDevice.isVirtual()).thenReturn(false);
+        when(mInputDevice.getVibrator().hasVibrator()).thenReturn(true);
+
+        assertThat(mController.isAvailable()).isTrue();
+    }
+
+    @Test
+    public void testIsAvailable_hasNoVibratingDevice_shouldReturnFalse() {
+        when(mInputManager.getInputDeviceIds()).thenReturn(new int[]{1});
+        when(mInputManager.getInputDevice(1)).thenReturn(mInputDevice);
+        when(mInputDevice.isVirtual()).thenReturn(false);
+        when(mInputDevice.getVibrator().hasVibrator()).thenReturn(false);
+
+        assertThat(mController.isAvailable()).isFalse();
+    }
+
+    @Test
+    public void testIsAvailable_hasNoPhysicalDevice_shouldReturnFalse() {
+        when(mInputManager.getInputDeviceIds()).thenReturn(new int[]{1});
+        when(mInputManager.getInputDevice(1)).thenReturn(mInputDevice);
+        when(mInputDevice.isVirtual()).thenReturn(true);
+
+        assertThat(mController.isAvailable()).isFalse();
+    }
+
+    @Test
+    public void testIsAvailable_hasNoDevice_shouldReturnFalse() {
+        when(mInputManager.getInputDeviceIds()).thenReturn(new int[]{});
+
+        assertThat(mController.isAvailable()).isFalse();
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/inputmethod/SpellCheckerPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/inputmethod/SpellCheckerPreferenceControllerTest.java
new file mode 100644 (file)
index 0000000..78c0590
--- /dev/null
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.inputmethod;
+
+import android.content.Context;
+import android.support.v7.preference.Preference;
+import android.view.textservice.SpellCheckerInfo;
+import android.view.textservice.TextServicesManager;
+
+import com.android.settings.R;
+import com.android.settings.SettingsRobolectricTestRunner;
+import com.android.settings.TestConfig;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Answers;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.annotation.Config;
+import org.robolectric.shadows.ShadowApplication;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class SpellCheckerPreferenceControllerTest {
+
+    @Mock(answer = Answers.RETURNS_DEEP_STUBS)
+    private Context mContext;
+    @Mock
+    private TextServicesManager mTextServicesManager;
+    private Context mAppContext;
+    private Preference mPreference;
+    private SpellCheckerPreferenceController mController;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mAppContext = ShadowApplication.getInstance().getApplicationContext();
+        when(mContext.getSystemService(Context.TEXT_SERVICES_MANAGER_SERVICE))
+                .thenReturn(mTextServicesManager);
+        mPreference = new Preference(mAppContext);
+        mController = new SpellCheckerPreferenceController(mContext);
+    }
+
+    @Test
+    public void updateState_NoSpellerChecker_shouldSetSummaryToDefault() {
+        when(mTextServicesManager.isSpellCheckerEnabled()).thenReturn(true);
+        when(mTextServicesManager.getCurrentSpellChecker()).thenReturn(null);
+
+        mController.updateState(mPreference);
+
+        assertThat(mPreference.getSummary())
+                .isEqualTo(mAppContext.getString(R.string.spell_checker_not_selected));
+    }
+
+    @Test
+    public void updateState_spellerCheckerDisabled_shouldSetSummaryToDisabledText() {
+        when(mTextServicesManager.isSpellCheckerEnabled()).thenReturn(false);
+
+        mController.updateState(mPreference);
+
+        assertThat(mPreference.getSummary())
+                .isEqualTo(mAppContext.getString(R.string.switch_off_text));
+    }
+
+    @Test
+    public void updateState_hasSpellerChecker_shouldSetSummaryToAppName() {
+        final String spellCheckerAppLabel = "test";
+        final SpellCheckerInfo sci = mock(SpellCheckerInfo.class);
+        when(mTextServicesManager.isSpellCheckerEnabled()).thenReturn(true);
+        when(mTextServicesManager.getCurrentSpellChecker()).thenReturn(sci);
+        when(sci.loadLabel(mContext.getPackageManager())).thenReturn(spellCheckerAppLabel);
+
+        mController.updateState(mPreference);
+
+        assertThat(mPreference.getSummary()).isEqualTo(spellCheckerAppLabel);
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/language/PhoneLanguagePreferenceControllerTest.java b/tests/robotests/src/com/android/settings/language/PhoneLanguagePreferenceControllerTest.java
new file mode 100644 (file)
index 0000000..e7367bb
--- /dev/null
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.language;
+
+import android.content.Context;
+import android.support.v7.preference.Preference;
+
+import com.android.settings.SettingsRobolectricTestRunner;
+import com.android.settings.TestConfig;
+import com.android.settings.testutils.FakeFeatureFactory;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Answers;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.annotation.Config;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class PhoneLanguagePreferenceControllerTest {
+
+    @Mock(answer = Answers.RETURNS_DEEP_STUBS)
+    private Context mContext;
+    @Mock
+    private Preference mPreference;
+    private FakeFeatureFactory mFeatureFactory;
+    private PhoneLanguagePreferenceController mController;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        FakeFeatureFactory.setupForTest(mContext);
+        mFeatureFactory = (FakeFeatureFactory) FakeFeatureFactory.getFactory(mContext);
+        mController = new PhoneLanguagePreferenceController(mContext);
+    }
+
+    @Test
+    public void testIsAvailable_hasMultipleLocales_shouldReturnTrue() {
+        when(mContext.getAssets().getLocales()).thenReturn(new String[]{"en", "de"});
+
+        assertThat(mController.isAvailable()).isTrue();
+    }
+
+    @Test
+    public void testIsAvailable_hasSingleLocales_shouldReturnFalse() {
+        when(mContext.getAssets().getLocales()).thenReturn(new String[]{"en"});
+
+        assertThat(mController.isAvailable()).isFalse();
+    }
+
+    @Test
+    public void testUpdateState_shouldUpdateSummary() {
+        final String testSummary = "test";
+        when(mFeatureFactory.localeFeatureProvider.getLocaleNames()).thenReturn(testSummary);
+
+        mController.updateState(mPreference);
+
+        verify(mPreference).setSummary(testSummary);
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/language/TtsPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/language/TtsPreferenceControllerTest.java
new file mode 100644 (file)
index 0000000..0e41784
--- /dev/null
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.language;
+
+import android.content.Context;
+import android.speech.tts.TextToSpeech;
+import android.speech.tts.TtsEngines;
+import android.support.v7.preference.Preference;
+import android.support.v7.preference.PreferenceScreen;
+
+import com.android.settings.SettingsRobolectricTestRunner;
+import com.android.settings.TestConfig;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Answers;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.annotation.Config;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class TtsPreferenceControllerTest {
+
+    @Mock(answer = Answers.RETURNS_DEEP_STUBS)
+    private Context mContext;
+    @Mock
+    private TtsEngines mTtsEngines;
+    @Mock
+    private PreferenceScreen mScreen;
+
+    private TtsPreferenceController mController;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        mController = new TtsPreferenceController(mContext, mTtsEngines);
+    }
+
+    @Test
+    public void testIsAvailable_ttsEngineEmpty_shouldReturnFalse() {
+
+        // Not available when there is no engine.
+        when(mTtsEngines.getEngines()).thenReturn(new ArrayList<>());
+
+        assertThat(mController.isAvailable()).isFalse();
+    }
+
+    @Test
+    public void testIsAvailable_ttsEngineInstalled_shouldReturnTrue() {
+        final List<TextToSpeech.EngineInfo> infolist = new ArrayList<>();
+        infolist.add(mock(TextToSpeech.EngineInfo.class));
+        when(mTtsEngines.getEngines()).thenReturn(infolist);
+
+        assertThat(mController.isAvailable()).isTrue();
+    }
+
+    @Test
+    public void displayPreference_notAvailable_shouldRemoveCategory() {
+        when(mScreen.findPreference(anyString())).thenReturn(mock(Preference.class));
+
+        mController.displayPreference(mScreen);
+
+        // Remove both category and preference.
+        verify(mScreen, times(2)).removePreference(any(Preference.class));
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/language/UserDictionaryPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/language/UserDictionaryPreferenceControllerTest.java
new file mode 100644 (file)
index 0000000..d2032ed
--- /dev/null
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.language;
+
+import android.content.Context;
+import android.speech.tts.TtsEngines;
+import android.support.v7.preference.Preference;
+
+import com.android.settings.SettingsRobolectricTestRunner;
+import com.android.settings.TestConfig;
+import com.android.settings.UserDictionarySettings;
+import com.android.settings.inputmethod.UserDictionaryList;
+import com.android.settings.testutils.FakeFeatureFactory;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Answers;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.annotation.Config;
+import org.robolectric.shadows.ShadowApplication;
+
+import java.util.TreeSet;
+
+import static com.google.common.truth.Truth.assertThat;
+
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class UserDictionaryPreferenceControllerTest {
+
+    @Mock(answer = Answers.RETURNS_DEEP_STUBS)
+    private Context mContext;
+    @Mock
+    private TtsEngines mTtsEngines;
+    private Preference mPreference;
+    private TestController mController;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        FakeFeatureFactory.setupForTest(mContext);
+        mController = new TestController(mContext);
+        mPreference = new Preference(ShadowApplication.getInstance().getApplicationContext());
+    }
+
+    @Test
+    public void testIsAvailable_noLocale_shouldReturnFalse() {
+        mController.mLocales = null;
+
+        assertThat(mController.isAvailable()).isFalse();
+    }
+
+    @Test
+    public void testIsAvailable_hasLocale_shouldReturnTrue() {
+        mController.mLocales.add("en");
+
+        assertThat(mController.isAvailable()).isTrue();
+    }
+
+    @Test
+    public void updateState_noLocale_setUserDictionarySettingsAsFragment() {
+        mController.updateState(mPreference);
+
+        assertThat(mPreference.getFragment())
+                .isEqualTo(UserDictionarySettings.class.getCanonicalName());
+    }
+
+    @Test
+    public void updateState_singleLocale_setUserDictionarySettingsAsFragment_setLocaleInExtra() {
+        mController.mLocales.add("en");
+
+        mController.updateState(mPreference);
+
+        assertThat(mPreference.getFragment())
+                .isEqualTo(UserDictionarySettings.class.getCanonicalName());
+        assertThat(mPreference.getExtras().getString("locale"))
+                .isEqualTo("en");
+    }
+
+    @Test
+    public void updateState_multiLocale_setUserDictionaryListAsFragment() {
+        mController.mLocales.add("en");
+        mController.mLocales.add("de");
+        mController.updateState(mPreference);
+
+        assertThat(mPreference.getFragment())
+                .isEqualTo(UserDictionaryList.class.getCanonicalName());
+    }
+
+    /**
+     * Fake Controller that overrides getDictionaryLocales to make testing the rest of stuff easier.
+     */
+    private class TestController extends UserDictionaryPreferenceController {
+
+        private TreeSet<String> mLocales = new TreeSet<>();
+
+        @Override
+        protected TreeSet<String> getDictionaryLocales() {
+            return mLocales;
+        }
+
+        public TestController(Context context) {
+            super(context);
+        }
+    }
+
+}