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