SettingsFragment.java revision 5a5ee95faead8a2ae749067716481e86faf5f113
1/*
2 * Copyright (C) 2008 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.inputmethod.latin.settings;
18
19import android.app.Activity;
20import android.app.backup.BackupManager;
21import android.content.Context;
22import android.content.Intent;
23import android.content.SharedPreferences;
24import android.content.pm.PackageManager;
25import android.content.pm.ResolveInfo;
26import android.content.res.Resources;
27import android.media.AudioManager;
28import android.os.Build;
29import android.os.Bundle;
30import android.preference.ListPreference;
31import android.preference.Preference;
32import android.preference.PreferenceGroup;
33import android.preference.PreferenceScreen;
34import android.preference.TwoStatePreference;
35import android.util.Log;
36import android.view.Menu;
37import android.view.MenuInflater;
38import android.view.MenuItem;
39import android.view.inputmethod.InputMethodSubtype;
40
41import com.android.inputmethod.dictionarypack.DictionarySettingsActivity;
42import com.android.inputmethod.keyboard.KeyboardTheme;
43import com.android.inputmethod.latin.AudioAndHapticFeedbackManager;
44import com.android.inputmethod.latin.R;
45import com.android.inputmethod.latin.SubtypeSwitcher;
46import com.android.inputmethod.latin.define.ProductionFlags;
47import com.android.inputmethod.latin.setup.LauncherIconVisibilityManager;
48import com.android.inputmethod.latin.userdictionary.UserDictionaryList;
49import com.android.inputmethod.latin.userdictionary.UserDictionarySettings;
50import com.android.inputmethod.latin.utils.AdditionalSubtypeUtils;
51import com.android.inputmethod.latin.utils.ApplicationUtils;
52import com.android.inputmethod.latin.utils.FeedbackUtils;
53import com.android.inputmethod.latin.utils.SubtypeLocaleUtils;
54import com.android.inputmethodcommon.InputMethodSettingsFragment;
55
56import java.util.TreeSet;
57
58public final class SettingsFragment extends InputMethodSettingsFragment
59        implements SharedPreferences.OnSharedPreferenceChangeListener {
60    private static final String TAG = SettingsFragment.class.getSimpleName();
61    private static final boolean DBG_USE_INTERNAL_PERSONAL_DICTIONARY_SETTINGS = false;
62    private static final boolean USE_INTERNAL_PERSONAL_DICTIONARY_SETTIGS =
63            DBG_USE_INTERNAL_PERSONAL_DICTIONARY_SETTINGS
64            || Build.VERSION.SDK_INT <= Build.VERSION_CODES.JELLY_BEAN_MR2;
65
66    private static final int NO_MENU_GROUP = Menu.NONE; // We don't care about menu grouping.
67    private static final int MENU_FEEDBACK = Menu.FIRST; // The first menu item id and order.
68    private static final int MENU_ABOUT = Menu.FIRST + 1; // The second menu item id and order.
69
70    private void setPreferenceEnabled(final String preferenceKey, final boolean enabled) {
71        final Preference preference = findPreference(preferenceKey);
72        if (preference != null) {
73            preference.setEnabled(enabled);
74        }
75    }
76
77    private void updateListPreferenceSummaryToCurrentValue(final String prefKey) {
78        // Because the "%s" summary trick of {@link ListPreference} doesn't work properly before
79        // KitKat, we need to update the summary programmatically.
80        final ListPreference listPreference = (ListPreference)findPreference(prefKey);
81        if (listPreference == null) {
82            return;
83        }
84        final CharSequence entries[] = listPreference.getEntries();
85        final int entryIndex = listPreference.findIndexOfValue(listPreference.getValue());
86        listPreference.setSummary(entryIndex < 0 ? null : entries[entryIndex]);
87    }
88
89    private static void removePreference(final String preferenceKey, final PreferenceGroup parent) {
90        if (parent == null) {
91            return;
92        }
93        final Preference preference = parent.findPreference(preferenceKey);
94        if (preference != null) {
95            parent.removePreference(preference);
96        }
97    }
98
99    @Override
100    public void onCreate(final Bundle icicle) {
101        super.onCreate(icicle);
102        setHasOptionsMenu(true);
103        setInputMethodSettingsCategoryTitle(R.string.language_selection_title);
104        setSubtypeEnablerTitle(R.string.select_language);
105        addPreferencesFromResource(R.xml.prefs);
106        final PreferenceScreen preferenceScreen = getPreferenceScreen();
107        TwoStatePreferenceHelper.replaceCheckBoxPreferencesBySwitchPreferences(preferenceScreen);
108        preferenceScreen.setTitle(
109                ApplicationUtils.getActivityTitleResId(getActivity(), SettingsActivity.class));
110
111        final Resources res = getResources();
112        final Context context = getActivity();
113
114        // When we are called from the Settings application but we are not already running, some
115        // singleton and utility classes may not have been initialized.  We have to call
116        // initialization method of these classes here. See {@link LatinIME#onCreate()}.
117        SubtypeSwitcher.init(context);
118        SubtypeLocaleUtils.init(context);
119        AudioAndHapticFeedbackManager.init(context);
120
121        final SharedPreferences prefs = getPreferenceManager().getSharedPreferences();
122        prefs.registerOnSharedPreferenceChangeListener(this);
123
124        ensureConsistencyOfAutoCorrectionSettings();
125
126        final PreferenceScreen inputScreen =
127                (PreferenceScreen) findPreference(Settings.SCREEN_INPUT);
128        final PreferenceScreen multiLingualScreen =
129                (PreferenceScreen) findPreference(Settings.SCREEN_MULTI_LINGUAL);
130        final PreferenceScreen gestureScreen =
131                (PreferenceScreen) findPreference(Settings.SCREEN_GESTURE);
132        final PreferenceScreen correctionScreen =
133                (PreferenceScreen) findPreference(Settings.SCREEN_CORRECTION);
134        final PreferenceScreen advancedScreen =
135                (PreferenceScreen) findPreference(Settings.SCREEN_ADVANCED);
136        final PreferenceScreen debugScreen =
137                (PreferenceScreen) findPreference(Settings.SCREEN_DEBUG);
138
139        if (Settings.isInternal(prefs)) {
140            final Intent debugSettingsIntent = new Intent(Intent.ACTION_MAIN);
141            debugSettingsIntent.setClassName(
142                    context.getPackageName(), DebugSettingsActivity.class.getName());
143            debugScreen.setIntent(debugSettingsIntent);
144        } else {
145            advancedScreen.removePreference(debugScreen);
146        }
147
148        final boolean showVoiceKeyOption = res.getBoolean(
149                R.bool.config_enable_show_voice_key_option);
150        if (!showVoiceKeyOption) {
151            removePreference(Settings.PREF_VOICE_INPUT_KEY, inputScreen);
152        }
153
154        if (!AudioAndHapticFeedbackManager.getInstance().hasVibrator()) {
155            removePreference(Settings.PREF_VIBRATE_ON, inputScreen);
156            removePreference(Settings.PREF_VIBRATION_DURATION_SETTINGS, advancedScreen);
157        }
158        if (!Settings.ENABLE_SHOW_LANGUAGE_SWITCH_KEY_SETTINGS) {
159            removePreference(Settings.PREF_SHOW_LANGUAGE_SWITCH_KEY, multiLingualScreen);
160            removePreference(
161                    Settings.PREF_INCLUDE_OTHER_IMES_IN_LANGUAGE_SWITCH_LIST, multiLingualScreen);
162        }
163
164        // TODO: consolidate key preview dismiss delay with the key preview animation parameters.
165        if (!Settings.readFromBuildConfigIfToShowKeyPreviewPopupOption(res)) {
166            removePreference(Settings.PREF_POPUP_ON, inputScreen);
167            removePreference(Settings.PREF_KEY_PREVIEW_POPUP_DISMISS_DELAY, advancedScreen);
168        } else {
169            // TODO: Cleanup this setup.
170            final ListPreference keyPreviewPopupDismissDelay =
171                    (ListPreference) findPreference(Settings.PREF_KEY_PREVIEW_POPUP_DISMISS_DELAY);
172            final String popupDismissDelayDefaultValue = Integer.toString(res.getInteger(
173                    R.integer.config_key_preview_linger_timeout));
174            keyPreviewPopupDismissDelay.setEntries(new String[] {
175                    res.getString(R.string.key_preview_popup_dismiss_no_delay),
176                    res.getString(R.string.key_preview_popup_dismiss_default_delay),
177            });
178            keyPreviewPopupDismissDelay.setEntryValues(new String[] {
179                    "0",
180                    popupDismissDelayDefaultValue
181            });
182            if (null == keyPreviewPopupDismissDelay.getValue()) {
183                keyPreviewPopupDismissDelay.setValue(popupDismissDelayDefaultValue);
184            }
185            keyPreviewPopupDismissDelay.setEnabled(
186                    Settings.readKeyPreviewPopupEnabled(prefs, res));
187        }
188
189        if (!res.getBoolean(R.bool.config_setup_wizard_available)) {
190            removePreference(Settings.PREF_SHOW_SETUP_WIZARD_ICON, advancedScreen);
191        }
192
193        final PreferenceScreen dictionaryLink =
194                (PreferenceScreen) findPreference(Settings.PREF_CONFIGURE_DICTIONARIES_KEY);
195        final Intent intent = dictionaryLink.getIntent();
196        intent.setClassName(context.getPackageName(), DictionarySettingsActivity.class.getName());
197        final int number = context.getPackageManager().queryIntentActivities(intent, 0).size();
198        if (0 >= number) {
199            correctionScreen.removePreference(dictionaryLink);
200        }
201
202        if (ProductionFlags.IS_METRICS_LOGGING_SUPPORTED) {
203            final Preference enableMetricsLogging =
204                    findPreference(Settings.PREF_ENABLE_METRICS_LOGGING);
205            if (enableMetricsLogging != null) {
206                final int applicationLabelRes = context.getApplicationInfo().labelRes;
207                final String applicationName = res.getString(applicationLabelRes);
208                final String enableMetricsLoggingTitle = res.getString(
209                        R.string.enable_metrics_logging, applicationName);
210                enableMetricsLogging.setTitle(enableMetricsLoggingTitle);
211            }
212        } else {
213            removePreference(Settings.PREF_ENABLE_METRICS_LOGGING, advancedScreen);
214        }
215
216        final Preference editPersonalDictionary =
217                findPreference(Settings.PREF_EDIT_PERSONAL_DICTIONARY);
218        final Intent editPersonalDictionaryIntent = editPersonalDictionary.getIntent();
219        final ResolveInfo ri = USE_INTERNAL_PERSONAL_DICTIONARY_SETTIGS ? null
220                : context.getPackageManager().resolveActivity(
221                        editPersonalDictionaryIntent, PackageManager.MATCH_DEFAULT_ONLY);
222        if (ri == null) {
223            overwriteUserDictionaryPreference(editPersonalDictionary);
224        }
225
226        if (!Settings.readFromBuildConfigIfGestureInputEnabled(res)) {
227            getPreferenceScreen().removePreference(gestureScreen);
228        }
229
230        AdditionalFeaturesSettingUtils.addAdditionalFeaturesPreferences(context, this);
231
232        setupKeypressVibrationDurationSettings(prefs, res);
233        setupKeypressSoundVolumeSettings(prefs, res);
234        refreshEnablingsOfKeypressSoundAndVibrationSettings(prefs, res);
235    }
236
237    @Override
238    public void onResume() {
239        super.onResume();
240        final SharedPreferences prefs = getPreferenceManager().getSharedPreferences();
241        final Resources res = getResources();
242        final Preference voiceInputKeyOption = findPreference(Settings.PREF_VOICE_INPUT_KEY);
243        if (voiceInputKeyOption != null) {
244            final boolean isShortcutImeEnabled = SubtypeSwitcher.getInstance()
245                    .isShortcutImeEnabled();
246            voiceInputKeyOption.setEnabled(isShortcutImeEnabled);
247            voiceInputKeyOption.setSummary(isShortcutImeEnabled ? null
248                    : res.getText(R.string.voice_input_disabled_summary));
249        }
250        final TwoStatePreference showSetupWizardIcon =
251                (TwoStatePreference)findPreference(Settings.PREF_SHOW_SETUP_WIZARD_ICON);
252        if (showSetupWizardIcon != null) {
253            showSetupWizardIcon.setChecked(Settings.readShowSetupWizardIcon(prefs, getActivity()));
254        }
255        updateListPreferenceSummaryToCurrentValue(Settings.PREF_SHOW_SUGGESTIONS_SETTING);
256        updateListPreferenceSummaryToCurrentValue(Settings.PREF_KEY_PREVIEW_POPUP_DISMISS_DELAY);
257        final ListPreference keyboardThemePref = (ListPreference)findPreference(
258                Settings.PREF_KEYBOARD_THEME);
259        if (keyboardThemePref != null) {
260            final KeyboardTheme keyboardTheme = KeyboardTheme.getKeyboardTheme(prefs);
261            final String value = Integer.toString(keyboardTheme.mThemeId);
262            final CharSequence entries[] = keyboardThemePref.getEntries();
263            final int entryIndex = keyboardThemePref.findIndexOfValue(value);
264            keyboardThemePref.setSummary(entryIndex < 0 ? null : entries[entryIndex]);
265            keyboardThemePref.setValue(value);
266        }
267        updateCustomInputStylesSummary(prefs, res);
268    }
269
270    @Override
271    public void onPause() {
272        super.onPause();
273        final SharedPreferences prefs = getPreferenceManager().getSharedPreferences();
274        final ListPreference keyboardThemePref = (ListPreference)findPreference(
275                Settings.PREF_KEYBOARD_THEME);
276        if (keyboardThemePref != null) {
277            KeyboardTheme.saveKeyboardThemeId(keyboardThemePref.getValue(), prefs);
278        }
279    }
280
281    @Override
282    public void onDestroy() {
283        getPreferenceManager().getSharedPreferences().unregisterOnSharedPreferenceChangeListener(
284                this);
285        super.onDestroy();
286    }
287
288    @Override
289    public void onSharedPreferenceChanged(final SharedPreferences prefs, final String key) {
290        final Activity activity = getActivity();
291        if (activity == null) {
292            // TODO: Introduce a static function to register this class and ensure that
293            // onCreate must be called before "onSharedPreferenceChanged" is called.
294            Log.w(TAG, "onSharedPreferenceChanged called before activity starts.");
295            return;
296        }
297        (new BackupManager(activity)).dataChanged();
298        final Resources res = getResources();
299        if (key.equals(Settings.PREF_POPUP_ON)) {
300            setPreferenceEnabled(Settings.PREF_KEY_PREVIEW_POPUP_DISMISS_DELAY,
301                    Settings.readKeyPreviewPopupEnabled(prefs, res));
302        } else if (key.equals(Settings.PREF_SHOW_SETUP_WIZARD_ICON)) {
303            LauncherIconVisibilityManager.updateSetupWizardIconVisibility(getActivity());
304        }
305        ensureConsistencyOfAutoCorrectionSettings();
306        updateListPreferenceSummaryToCurrentValue(Settings.PREF_SHOW_SUGGESTIONS_SETTING);
307        updateListPreferenceSummaryToCurrentValue(Settings.PREF_KEY_PREVIEW_POPUP_DISMISS_DELAY);
308        updateListPreferenceSummaryToCurrentValue(Settings.PREF_KEYBOARD_THEME);
309        refreshEnablingsOfKeypressSoundAndVibrationSettings(prefs, getResources());
310    }
311
312    private void ensureConsistencyOfAutoCorrectionSettings() {
313        final String autoCorrectionOff = getResources().getString(
314                R.string.auto_correction_threshold_mode_index_off);
315        final ListPreference autoCorrectionThresholdPref = (ListPreference)findPreference(
316                Settings.PREF_AUTO_CORRECTION_THRESHOLD);
317        final String currentSetting = autoCorrectionThresholdPref.getValue();
318        setPreferenceEnabled(
319                Settings.PREF_BIGRAM_PREDICTIONS, !currentSetting.equals(autoCorrectionOff));
320    }
321
322    private void updateCustomInputStylesSummary(final SharedPreferences prefs,
323            final Resources res) {
324        final PreferenceScreen customInputStyles =
325                (PreferenceScreen)findPreference(Settings.PREF_CUSTOM_INPUT_STYLES);
326        final String prefSubtype = Settings.readPrefAdditionalSubtypes(prefs, res);
327        final InputMethodSubtype[] subtypes =
328                AdditionalSubtypeUtils.createAdditionalSubtypesArray(prefSubtype);
329        final StringBuilder styles = new StringBuilder();
330        for (final InputMethodSubtype subtype : subtypes) {
331            if (styles.length() > 0) styles.append(", ");
332            styles.append(SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(subtype));
333        }
334        customInputStyles.setSummary(styles);
335    }
336
337    private void refreshEnablingsOfKeypressSoundAndVibrationSettings(
338            final SharedPreferences sp, final Resources res) {
339        setPreferenceEnabled(Settings.PREF_VIBRATION_DURATION_SETTINGS,
340                Settings.readVibrationEnabled(sp, res));
341        setPreferenceEnabled(Settings.PREF_KEYPRESS_SOUND_VOLUME,
342                Settings.readKeypressSoundEnabled(sp, res));
343    }
344
345    private void setupKeypressVibrationDurationSettings(final SharedPreferences sp,
346            final Resources res) {
347        final SeekBarDialogPreference pref = (SeekBarDialogPreference)findPreference(
348                Settings.PREF_VIBRATION_DURATION_SETTINGS);
349        if (pref == null) {
350            return;
351        }
352        pref.setInterface(new SeekBarDialogPreference.ValueProxy() {
353            @Override
354            public void writeValue(final int value, final String key) {
355                sp.edit().putInt(key, value).apply();
356            }
357
358            @Override
359            public void writeDefaultValue(final String key) {
360                sp.edit().remove(key).apply();
361            }
362
363            @Override
364            public int readValue(final String key) {
365                return Settings.readKeypressVibrationDuration(sp, res);
366            }
367
368            @Override
369            public int readDefaultValue(final String key) {
370                return Settings.readDefaultKeypressVibrationDuration(res);
371            }
372
373            @Override
374            public void feedbackValue(final int value) {
375                AudioAndHapticFeedbackManager.getInstance().vibrate(value);
376            }
377
378            @Override
379            public String getValueText(final int value) {
380                if (value < 0) {
381                    return res.getString(R.string.settings_system_default);
382                }
383                return res.getString(R.string.abbreviation_unit_milliseconds, value);
384            }
385        });
386    }
387
388    private void setupKeypressSoundVolumeSettings(final SharedPreferences sp, final Resources res) {
389        final SeekBarDialogPreference pref = (SeekBarDialogPreference)findPreference(
390                Settings.PREF_KEYPRESS_SOUND_VOLUME);
391        if (pref == null) {
392            return;
393        }
394        final AudioManager am = (AudioManager)getActivity().getSystemService(Context.AUDIO_SERVICE);
395        pref.setInterface(new SeekBarDialogPreference.ValueProxy() {
396            private static final float PERCENTAGE_FLOAT = 100.0f;
397
398            private float getValueFromPercentage(final int percentage) {
399                return percentage / PERCENTAGE_FLOAT;
400            }
401
402            private int getPercentageFromValue(final float floatValue) {
403                return (int)(floatValue * PERCENTAGE_FLOAT);
404            }
405
406            @Override
407            public void writeValue(final int value, final String key) {
408                sp.edit().putFloat(key, getValueFromPercentage(value)).apply();
409            }
410
411            @Override
412            public void writeDefaultValue(final String key) {
413                sp.edit().remove(key).apply();
414            }
415
416            @Override
417            public int readValue(final String key) {
418                return getPercentageFromValue(Settings.readKeypressSoundVolume(sp, res));
419            }
420
421            @Override
422            public int readDefaultValue(final String key) {
423                return getPercentageFromValue(Settings.readDefaultKeypressSoundVolume(res));
424            }
425
426            @Override
427            public String getValueText(final int value) {
428                if (value < 0) {
429                    return res.getString(R.string.settings_system_default);
430                }
431                return Integer.toString(value);
432            }
433
434            @Override
435            public void feedbackValue(final int value) {
436                am.playSoundEffect(
437                        AudioManager.FX_KEYPRESS_STANDARD, getValueFromPercentage(value));
438            }
439        });
440    }
441
442    private void overwriteUserDictionaryPreference(Preference userDictionaryPreference) {
443        final Activity activity = getActivity();
444        final TreeSet<String> localeList = UserDictionaryList.getUserDictionaryLocalesSet(activity);
445        if (null == localeList) {
446            // The locale list is null if and only if the user dictionary service is
447            // not present or disabled. In this case we need to remove the preference.
448            getPreferenceScreen().removePreference(userDictionaryPreference);
449        } else if (localeList.size() <= 1) {
450            userDictionaryPreference.setFragment(UserDictionarySettings.class.getName());
451            // If the size of localeList is 0, we don't set the locale parameter in the
452            // extras. This will be interpreted by the UserDictionarySettings class as
453            // meaning "the current locale".
454            // Note that with the current code for UserDictionaryList#getUserDictionaryLocalesSet()
455            // the locale list always has at least one element, since it always includes the current
456            // locale explicitly. @see UserDictionaryList.getUserDictionaryLocalesSet().
457            if (localeList.size() == 1) {
458                final String locale = (String)localeList.toArray()[0];
459                userDictionaryPreference.getExtras().putString("locale", locale);
460            }
461        } else {
462            userDictionaryPreference.setFragment(UserDictionaryList.class.getName());
463        }
464    }
465
466    @Override
467    public void onCreateOptionsMenu(final Menu menu, final MenuInflater inflater) {
468        if (FeedbackUtils.isFeedbackFormSupported()) {
469            menu.add(NO_MENU_GROUP, MENU_FEEDBACK /* itemId */, MENU_FEEDBACK /* order */,
470                    R.string.send_feedback);
471        }
472        final int aboutResId = FeedbackUtils.getAboutKeyboardTitleResId();
473        if (aboutResId != 0) {
474            menu.add(NO_MENU_GROUP, MENU_ABOUT /* itemId */, MENU_ABOUT /* order */, aboutResId);
475        }
476    }
477
478    @Override
479    public boolean onOptionsItemSelected(final MenuItem item) {
480        final int itemId = item.getItemId();
481        if (itemId == MENU_FEEDBACK) {
482            FeedbackUtils.showFeedbackForm(getActivity());
483            return true;
484        }
485        if (itemId == MENU_ABOUT) {
486            final Intent aboutIntent = FeedbackUtils.getAboutKeyboardIntent(getActivity());
487            if (aboutIntent != null) {
488                startActivity(aboutIntent);
489                return true;
490            }
491        }
492        return super.onOptionsItemSelected(item);
493    }
494}
495