Settings.java revision f1f5ed542d43566f30e9f03f98de9bef717465ce
1/*
2 * Copyright (C) 2013 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.content.Context;
20import android.content.SharedPreferences;
21import android.content.pm.ApplicationInfo;
22import android.content.res.Resources;
23import android.preference.PreferenceManager;
24import android.util.Log;
25
26import com.android.inputmethod.keyboard.KeyboardSwitcher;
27import com.android.inputmethod.latin.AudioAndHapticFeedbackManager;
28import com.android.inputmethod.latin.InputAttributes;
29import com.android.inputmethod.latin.R;
30import com.android.inputmethod.latin.utils.AdditionalSubtypeUtils;
31import com.android.inputmethod.latin.utils.ResourceUtils;
32import com.android.inputmethod.latin.utils.RunInLocale;
33import com.android.inputmethod.latin.utils.StringUtils;
34
35import java.util.Collections;
36import java.util.Locale;
37import java.util.Set;
38import java.util.concurrent.locks.ReentrantLock;
39
40public final class Settings implements SharedPreferences.OnSharedPreferenceChangeListener {
41    private static final String TAG = Settings.class.getSimpleName();
42    // In the same order as xml/prefs.xml
43    public static final String PREF_GENERAL_SETTINGS = "general_settings";
44    public static final String PREF_AUTO_CAP = "auto_cap";
45    public static final String PREF_VIBRATE_ON = "vibrate_on";
46    public static final String PREF_SOUND_ON = "sound_on";
47    public static final String PREF_POPUP_ON = "popup_on";
48    // PREF_VOICE_MODE_OBSOLETE is obsolete. Use PREF_VOICE_INPUT_KEY instead.
49    public static final String PREF_VOICE_MODE_OBSOLETE = "voice_mode";
50    public static final String PREF_VOICE_INPUT_KEY = "pref_voice_input_key";
51    public static final String PREF_CORRECTION_SETTINGS = "correction_settings";
52    public static final String PREF_EDIT_PERSONAL_DICTIONARY = "edit_personal_dictionary";
53    public static final String PREF_CONFIGURE_DICTIONARIES_KEY = "configure_dictionaries_key";
54    public static final String PREF_AUTO_CORRECTION_THRESHOLD = "auto_correction_threshold";
55    public static final String PREF_SHOW_SUGGESTIONS_SETTING = "show_suggestions_setting";
56    public static final String PREF_MISC_SETTINGS = "misc_settings";
57    public static final String PREF_ADVANCED_SETTINGS = "pref_advanced_settings";
58    public static final String PREF_KEY_USE_CONTACTS_DICT = "pref_key_use_contacts_dict";
59    public static final String PREF_KEY_USE_PERSONALIZED_DICTS = "pref_key_use_personalized_dicts";
60    public static final String PREF_KEY_USE_DOUBLE_SPACE_PERIOD =
61            "pref_key_use_double_space_period";
62    public static final String PREF_BLOCK_POTENTIALLY_OFFENSIVE =
63            "pref_key_block_potentially_offensive";
64    public static final String PREF_SHOW_LANGUAGE_SWITCH_KEY =
65            "pref_show_language_switch_key";
66    public static final String PREF_INCLUDE_OTHER_IMES_IN_LANGUAGE_SWITCH_LIST =
67            "pref_include_other_imes_in_language_switch_list";
68    public static final String PREF_KEYBOARD_LAYOUT = "pref_keyboard_layout_20110916";
69    public static final String PREF_CUSTOM_INPUT_STYLES = "custom_input_styles";
70    // TODO: consolidate key preview dismiss delay with the key preview animation parameters.
71    public static final String PREF_KEY_PREVIEW_POPUP_DISMISS_DELAY =
72            "pref_key_preview_popup_dismiss_delay";
73    public static final String PREF_BIGRAM_PREDICTIONS = "next_word_prediction";
74    public static final String PREF_GESTURE_SETTINGS = "gesture_typing_settings";
75    public static final String PREF_GESTURE_INPUT = "gesture_input";
76    public static final String PREF_SLIDING_KEY_INPUT_PREVIEW = "pref_sliding_key_input_preview";
77    public static final String PREF_KEY_LONGPRESS_TIMEOUT = "pref_key_longpress_timeout";
78    public static final String PREF_VIBRATION_DURATION_SETTINGS =
79            "pref_vibration_duration_settings";
80    public static final String PREF_KEYPRESS_SOUND_VOLUME =
81            "pref_keypress_sound_volume";
82    public static final String PREF_GESTURE_PREVIEW_TRAIL = "pref_gesture_preview_trail";
83    public static final String PREF_GESTURE_FLOATING_PREVIEW_TEXT =
84            "pref_gesture_floating_preview_text";
85    public static final String PREF_SHOW_SETUP_WIZARD_ICON = "pref_show_setup_wizard_icon";
86    public static final String PREF_PHRASE_GESTURE_ENABLED = "pref_gesture_space_aware";
87
88    public static final String PREF_INPUT_LANGUAGE = "input_language";
89    public static final String PREF_SELECTED_LANGUAGES = "selected_languages";
90    public static final String PREF_DEBUG_SETTINGS = "debug_settings";
91    public static final String PREF_KEY_IS_INTERNAL = "pref_key_is_internal";
92
93    // This preference key is deprecated. Use {@link #PREF_SHOW_LANGUAGE_SWITCH_KEY} instead.
94    // This is being used only for the backward compatibility.
95    private static final String PREF_SUPPRESS_LANGUAGE_SWITCH_KEY =
96            "pref_suppress_language_switch_key";
97
98    private static final String PREF_LAST_USED_PERSONALIZATION_TOKEN =
99            "pref_last_used_personalization_token";
100    private static final String PREF_LAST_PERSONALIZATION_DICT_WIPED_TIME =
101            "pref_last_used_personalization_dict_wiped_time";
102    private static final String PREF_CORPUS_HANDLES_FOR_PERSONALIZATION =
103            "pref_corpus_handles_for_personalization";
104    public static final String PREF_SEND_FEEDBACK = "send_feedback";
105    public static final String PREF_ABOUT_KEYBOARD = "about_keyboard";
106
107    // Emoji
108    public static final String PREF_EMOJI_RECENT_KEYS = "emoji_recent_keys";
109    public static final String PREF_EMOJI_CATEGORY_LAST_TYPED_ID = "emoji_category_last_typed_id";
110    public static final String PREF_LAST_SHOWN_EMOJI_CATEGORY_ID = "last_shown_emoji_category_id";
111
112    private static final float UNDEFINED_PREFERENCE_VALUE_FLOAT = -1.0f;
113    private static final int UNDEFINED_PREFERENCE_VALUE_INT = -1;
114
115    private Context mContext;
116    private Resources mRes;
117    private SharedPreferences mPrefs;
118    private SettingsValues mSettingsValues;
119    private final ReentrantLock mSettingsValuesLock = new ReentrantLock();
120
121    private static final Settings sInstance = new Settings();
122
123    public static Settings getInstance() {
124        return sInstance;
125    }
126
127    public static void init(final Context context) {
128        sInstance.onCreate(context);
129    }
130
131    private Settings() {
132        // Intentional empty constructor for singleton.
133    }
134
135    private void onCreate(final Context context) {
136        mContext = context;
137        mRes = context.getResources();
138        mPrefs = PreferenceManager.getDefaultSharedPreferences(context);
139        mPrefs.registerOnSharedPreferenceChangeListener(this);
140    }
141
142    public void onDestroy() {
143        mPrefs.unregisterOnSharedPreferenceChangeListener(this);
144    }
145
146    @Override
147    public void onSharedPreferenceChanged(final SharedPreferences prefs, final String key) {
148        mSettingsValuesLock.lock();
149        try {
150            if (mSettingsValues == null) {
151                // TODO: Introduce a static function to register this class and ensure that
152                // loadSettings must be called before "onSharedPreferenceChanged" is called.
153                Log.w(TAG, "onSharedPreferenceChanged called before loadSettings.");
154                return;
155            }
156            loadSettings(mContext, mSettingsValues.mLocale, mSettingsValues.mInputAttributes);
157        } finally {
158            mSettingsValuesLock.unlock();
159        }
160    }
161
162    public void loadSettings(final Context context, final Locale locale,
163            final InputAttributes inputAttributes) {
164        mSettingsValuesLock.lock();
165        mContext = context;
166        try {
167            final SharedPreferences prefs = mPrefs;
168            final RunInLocale<SettingsValues> job = new RunInLocale<SettingsValues>() {
169                @Override
170                protected SettingsValues job(final Resources res) {
171                    return new SettingsValues(context, prefs, res, inputAttributes);
172                }
173            };
174            mSettingsValues = job.runInLocale(mRes, locale);
175        } finally {
176            mSettingsValuesLock.unlock();
177        }
178    }
179
180    // TODO: Remove this method and add proxy method to SettingsValues.
181    public SettingsValues getCurrent() {
182        return mSettingsValues;
183    }
184
185    public boolean isInternal() {
186        return mSettingsValues.mIsInternal;
187    }
188
189    public boolean isWordSeparator(final int code) {
190        return mSettingsValues.isWordSeparator(code);
191    }
192
193    public boolean getBlockPotentiallyOffensive() {
194        return mSettingsValues.mBlockPotentiallyOffensive;
195    }
196
197    // Accessed from the settings interface, hence public
198    public static boolean readKeypressSoundEnabled(final SharedPreferences prefs,
199            final Resources res) {
200        return prefs.getBoolean(Settings.PREF_SOUND_ON,
201                res.getBoolean(R.bool.config_default_sound_enabled));
202    }
203
204    public static boolean readVibrationEnabled(final SharedPreferences prefs,
205            final Resources res) {
206        final boolean hasVibrator = AudioAndHapticFeedbackManager.getInstance().hasVibrator();
207        return hasVibrator && prefs.getBoolean(PREF_VIBRATE_ON,
208                res.getBoolean(R.bool.config_default_vibration_enabled));
209    }
210
211    public static boolean readAutoCorrectEnabled(final String currentAutoCorrectionSetting,
212            final Resources res) {
213        final String autoCorrectionOff = res.getString(
214                R.string.auto_correction_threshold_mode_index_off);
215        return !currentAutoCorrectionSetting.equals(autoCorrectionOff);
216    }
217
218    public static boolean readBlockPotentiallyOffensive(final SharedPreferences prefs,
219            final Resources res) {
220        return prefs.getBoolean(Settings.PREF_BLOCK_POTENTIALLY_OFFENSIVE,
221                res.getBoolean(R.bool.config_block_potentially_offensive));
222    }
223
224    public static boolean readFromBuildConfigIfGestureInputEnabled(final Resources res) {
225        return res.getBoolean(R.bool.config_gesture_input_enabled_by_build_config);
226    }
227
228    public static boolean readGestureInputEnabled(final SharedPreferences prefs,
229            final Resources res) {
230        return readFromBuildConfigIfGestureInputEnabled(res)
231                && prefs.getBoolean(Settings.PREF_GESTURE_INPUT, true);
232    }
233
234    public static boolean readPhraseGestureEnabled(final SharedPreferences prefs,
235            final Resources res) {
236        return prefs.getBoolean(Settings.PREF_PHRASE_GESTURE_ENABLED,
237                res.getBoolean(R.bool.config_default_phrase_gesture_enabled));
238    }
239
240    public static boolean readFromBuildConfigIfToShowKeyPreviewPopupOption(final Resources res) {
241        return res.getBoolean(R.bool.config_enable_show_key_preview_popup_option);
242    }
243
244    public static boolean readKeyPreviewPopupEnabled(final SharedPreferences prefs,
245            final Resources res) {
246        final boolean defaultKeyPreviewPopup = res.getBoolean(
247                R.bool.config_default_key_preview_popup);
248        if (!readFromBuildConfigIfToShowKeyPreviewPopupOption(res)) {
249            return defaultKeyPreviewPopup;
250        }
251        return prefs.getBoolean(PREF_POPUP_ON, defaultKeyPreviewPopup);
252    }
253
254    public static int readKeyPreviewPopupDismissDelay(final SharedPreferences prefs,
255            final Resources res) {
256        return Integer.parseInt(prefs.getString(PREF_KEY_PREVIEW_POPUP_DISMISS_DELAY,
257                Integer.toString(res.getInteger(
258                        R.integer.config_key_preview_linger_timeout))));
259    }
260
261    public static boolean readShowsLanguageSwitchKey(final SharedPreferences prefs) {
262        if (prefs.contains(PREF_SUPPRESS_LANGUAGE_SWITCH_KEY)) {
263            final boolean suppressLanguageSwitchKey = prefs.getBoolean(
264                    PREF_SUPPRESS_LANGUAGE_SWITCH_KEY, false);
265            final SharedPreferences.Editor editor = prefs.edit();
266            editor.remove(PREF_SUPPRESS_LANGUAGE_SWITCH_KEY);
267            editor.putBoolean(PREF_SHOW_LANGUAGE_SWITCH_KEY, !suppressLanguageSwitchKey);
268            editor.apply();
269        }
270        return prefs.getBoolean(PREF_SHOW_LANGUAGE_SWITCH_KEY, true);
271    }
272
273    public static int readKeyboardThemeIndex(final SharedPreferences prefs, final Resources res) {
274        final int defaultThemeIndex = readDefaultKeyboardThemeIndex(res);
275        final String themeIndexString = prefs.getString(PREF_KEYBOARD_LAYOUT, null);
276        if (themeIndexString == null) {
277            return defaultThemeIndex;
278        }
279        try {
280            return Integer.parseInt(themeIndexString);
281        } catch (final NumberFormatException e) {
282            // Format error, returns default keyboard theme index.
283            Log.e(TAG, "Illegal keyboard theme in preference: " + themeIndexString + ", default to "
284                    + defaultThemeIndex, e);
285        }
286        return defaultThemeIndex;
287    }
288
289    private static int readDefaultKeyboardThemeIndex(final Resources res) {
290        final String defaultThemeIndexString = res.getString(
291                R.string.config_default_keyboard_theme_index);
292        try {
293            return Integer.parseInt(defaultThemeIndexString);
294        } catch (final NumberFormatException e) {
295            final int defaultThemeIndex = KeyboardSwitcher.DEFAULT_THEME_INDEX;
296            Log.e(TAG, "Corrupted default keyoard theme in resource: " + defaultThemeIndexString
297                    + ", default to " + defaultThemeIndex, e);
298            return defaultThemeIndex;
299        }
300    }
301
302    public static int resetAndGetDefaultKeyboardThemeIndex(final SharedPreferences prefs,
303            final Resources res) {
304        final int defaultThemeIndex = readDefaultKeyboardThemeIndex(res);
305        prefs.edit().putString(PREF_KEYBOARD_LAYOUT, Integer.toString(defaultThemeIndex)).apply();
306        return defaultThemeIndex;
307    }
308
309    public static String readPrefAdditionalSubtypes(final SharedPreferences prefs,
310            final Resources res) {
311        final String predefinedPrefSubtypes = AdditionalSubtypeUtils.createPrefSubtypes(
312                res.getStringArray(R.array.predefined_subtypes));
313        return prefs.getString(PREF_CUSTOM_INPUT_STYLES, predefinedPrefSubtypes);
314    }
315
316    public static void writePrefAdditionalSubtypes(final SharedPreferences prefs,
317            final String prefSubtypes) {
318        prefs.edit().putString(Settings.PREF_CUSTOM_INPUT_STYLES, prefSubtypes).apply();
319    }
320
321    public static float readKeypressSoundVolume(final SharedPreferences prefs,
322            final Resources res) {
323        final float volume = prefs.getFloat(
324                PREF_KEYPRESS_SOUND_VOLUME, UNDEFINED_PREFERENCE_VALUE_FLOAT);
325        return (volume != UNDEFINED_PREFERENCE_VALUE_FLOAT) ? volume
326                : readDefaultKeypressSoundVolume(res);
327    }
328
329    // Default keypress sound volume for unknown devices.
330    // The negative value means system default.
331    private static final String DEFAULT_KEYPRESS_SOUND_VOLUME = Float.toString(-1.0f);
332
333    public static float readDefaultKeypressSoundVolume(final Resources res) {
334        return Float.parseFloat(ResourceUtils.getDeviceOverrideValue(res,
335                R.array.keypress_volumes, DEFAULT_KEYPRESS_SOUND_VOLUME));
336    }
337
338    public static int readKeyLongpressTimeout(final SharedPreferences prefs,
339            final Resources res) {
340        final int milliseconds = prefs.getInt(
341                PREF_KEY_LONGPRESS_TIMEOUT, UNDEFINED_PREFERENCE_VALUE_INT);
342        return (milliseconds != UNDEFINED_PREFERENCE_VALUE_INT) ? milliseconds
343                : readDefaultKeyLongpressTimeout(res);
344    }
345
346    public static int readDefaultKeyLongpressTimeout(final Resources res) {
347        return res.getInteger(R.integer.config_default_longpress_key_timeout);
348    }
349
350    public static int readKeypressVibrationDuration(final SharedPreferences prefs,
351            final Resources res) {
352        final int milliseconds = prefs.getInt(
353                PREF_VIBRATION_DURATION_SETTINGS, UNDEFINED_PREFERENCE_VALUE_INT);
354        return (milliseconds != UNDEFINED_PREFERENCE_VALUE_INT) ? milliseconds
355                : readDefaultKeypressVibrationDuration(res);
356    }
357
358    // Default keypress vibration duration for unknown devices.
359    // The negative value means system default.
360    private static final String DEFAULT_KEYPRESS_VIBRATION_DURATION = Integer.toString(-1);
361
362    public static int readDefaultKeypressVibrationDuration(final Resources res) {
363        return Integer.parseInt(ResourceUtils.getDeviceOverrideValue(res,
364                R.array.keypress_vibration_durations, DEFAULT_KEYPRESS_VIBRATION_DURATION));
365    }
366
367    public static boolean readUsabilityStudyMode(final SharedPreferences prefs) {
368        return prefs.getBoolean(DebugSettings.PREF_USABILITY_STUDY_MODE, true);
369    }
370
371    public static float readKeyPreviewAnimationScale(final SharedPreferences prefs,
372            final String prefKey, final float defaultValue) {
373        final float fraction = prefs.getFloat(prefKey, UNDEFINED_PREFERENCE_VALUE_FLOAT);
374        return (fraction != UNDEFINED_PREFERENCE_VALUE_FLOAT) ? fraction : defaultValue;
375    }
376
377    public static int readKeyPreviewAnimationDuration(final SharedPreferences prefs,
378            final String prefKey, final int defaultValue) {
379        final int milliseconds = prefs.getInt(prefKey, UNDEFINED_PREFERENCE_VALUE_INT);
380        return (milliseconds != UNDEFINED_PREFERENCE_VALUE_INT) ? milliseconds : defaultValue;
381    }
382
383    public static boolean readUseFullscreenMode(final Resources res) {
384        return res.getBoolean(R.bool.config_use_fullscreen_mode);
385    }
386
387    public static boolean readShowSetupWizardIcon(final SharedPreferences prefs,
388            final Context context) {
389        final boolean enableSetupWizardByConfig = context.getResources().getBoolean(
390                R.bool.config_setup_wizard_available);
391        if (!enableSetupWizardByConfig) {
392            return false;
393        }
394        if (!prefs.contains(Settings.PREF_SHOW_SETUP_WIZARD_ICON)) {
395            final ApplicationInfo appInfo = context.getApplicationInfo();
396            final boolean isApplicationInSystemImage =
397                    (appInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0;
398            // Default value
399            return !isApplicationInSystemImage;
400        }
401        return prefs.getBoolean(Settings.PREF_SHOW_SETUP_WIZARD_ICON, false);
402    }
403
404    public static boolean isInternal(final SharedPreferences prefs) {
405        return prefs.getBoolean(Settings.PREF_KEY_IS_INTERNAL, false);
406    }
407
408    public void writeLastUsedPersonalizationToken(byte[] token) {
409        if (token == null) {
410            mPrefs.edit().remove(PREF_LAST_USED_PERSONALIZATION_TOKEN).apply();
411        } else {
412            final String tokenStr = StringUtils.byteArrayToHexString(token);
413            mPrefs.edit().putString(PREF_LAST_USED_PERSONALIZATION_TOKEN, tokenStr).apply();
414        }
415    }
416
417    public byte[] readLastUsedPersonalizationToken() {
418        final String tokenStr = mPrefs.getString(PREF_LAST_USED_PERSONALIZATION_TOKEN, null);
419        return StringUtils.hexStringToByteArray(tokenStr);
420    }
421
422    public void writeLastPersonalizationDictWipedTime(final long timestamp) {
423        mPrefs.edit().putLong(PREF_LAST_PERSONALIZATION_DICT_WIPED_TIME, timestamp).apply();
424    }
425
426    public long readLastPersonalizationDictGeneratedTime() {
427        return mPrefs.getLong(PREF_LAST_PERSONALIZATION_DICT_WIPED_TIME, 0);
428    }
429
430    public void writeCorpusHandlesForPersonalization(final Set<String> corpusHandles) {
431        mPrefs.edit().putStringSet(PREF_CORPUS_HANDLES_FOR_PERSONALIZATION, corpusHandles).apply();
432    }
433
434    public Set<String> readCorpusHandlesForPersonalization() {
435        final Set<String> emptySet = Collections.emptySet();
436        return mPrefs.getStringSet(PREF_CORPUS_HANDLES_FOR_PERSONALIZATION, emptySet);
437    }
438
439    public static void writeEmojiRecentKeys(final SharedPreferences prefs, String str) {
440        prefs.edit().putString(PREF_EMOJI_RECENT_KEYS, str).apply();
441    }
442
443    public static String readEmojiRecentKeys(final SharedPreferences prefs) {
444        return prefs.getString(PREF_EMOJI_RECENT_KEYS, "");
445    }
446
447    public static void writeLastTypedEmojiCategoryPageId(
448            final SharedPreferences prefs, final int categoryId, final int categoryPageId) {
449        final String key = PREF_EMOJI_CATEGORY_LAST_TYPED_ID + categoryId;
450        prefs.edit().putInt(key, categoryPageId).apply();
451    }
452
453    public static int readLastTypedEmojiCategoryPageId(
454            final SharedPreferences prefs, final int categoryId) {
455        final String key = PREF_EMOJI_CATEGORY_LAST_TYPED_ID + categoryId;
456        return prefs.getInt(key, 0);
457    }
458
459    public static void writeLastShownEmojiCategoryId(
460            final SharedPreferences prefs, final int categoryId) {
461        prefs.edit().putInt(PREF_LAST_SHOWN_EMOJI_CATEGORY_ID, categoryId).apply();
462    }
463
464    public static int readLastShownEmojiCategoryId(
465            final SharedPreferences prefs, final int defValue) {
466        return prefs.getInt(PREF_LAST_SHOWN_EMOJI_CATEGORY_ID, defValue);
467    }
468}
469