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