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