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