Home | History | Annotate | Download | only in settings
      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 
    131     public static Settings getInstance() {
    132         return sInstance;
    133     }
    134 
    135     public static void init(final Context context) {
    136         sInstance.onCreate(context);
    137     }
    138 
    139     private Settings() {
    140         // Intentional empty constructor for singleton.
    141     }
    142 
    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 
    151     public void onDestroy() {
    152         mPrefs.unregisterOnSharedPreferenceChangeListener(this);
    153     }
    154 
    155     @Override
    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 
    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.
    191     public SettingsValues getCurrent() {
    192         return mSettingsValues;
    193     }
    194 
    195     public boolean isInternal() {
    196         return mSettingsValues.mIsInternal;
    197     }
    198 
    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
    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 
    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 
    217     public static boolean readAutoCorrectEnabled(final SharedPreferences prefs,
    218             final Resources res) {
    219         return prefs.getBoolean(PREF_AUTO_CORRECTION, true);
    220     }
    221 
    222     public static float readPlausibilityThreshold(final Resources res) {
    223         return Float.parseFloat(res.getString(R.string.plausibility_threshold));
    224     }
    225 
    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 
    232     public static boolean readFromBuildConfigIfGestureInputEnabled(final Resources res) {
    233         return res.getBoolean(R.bool.config_gesture_input_enabled_by_build_config);
    234     }
    235 
    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 
    242     public static boolean readFromBuildConfigIfToShowKeyPreviewPopupOption(final Resources res) {
    243         return res.getBoolean(R.bool.config_enable_show_key_preview_popup_option);
    244     }
    245 
    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 
    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 
    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 
    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 
    282     public static void writePrefAdditionalSubtypes(final SharedPreferences prefs,
    283             final String prefSubtypes) {
    284         prefs.edit().putString(PREF_CUSTOM_INPUT_STYLES, prefSubtypes).apply();
    285     }
    286 
    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 
    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 
    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 
    312     public static int readDefaultKeyLongpressTimeout(final Resources res) {
    313         return res.getInteger(R.integer.config_default_longpress_key_timeout);
    314     }
    315 
    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 
    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 
    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 
    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 
    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 
    352     public static boolean readUseFullscreenMode(final Resources res) {
    353         return res.getBoolean(R.bool.config_use_fullscreen_mode);
    354     }
    355 
    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 
    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 
    377     public static boolean isInternal(final SharedPreferences prefs) {
    378         return prefs.getBoolean(PREF_KEY_IS_INTERNAL, false);
    379     }
    380 
    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 
    390     public byte[] readLastUsedPersonalizationToken() {
    391         final String tokenStr = mPrefs.getString(PREF_LAST_USED_PERSONALIZATION_TOKEN, null);
    392         return StringUtils.hexStringToByteArray(tokenStr);
    393     }
    394 
    395     public void writeLastPersonalizationDictWipedTime(final long timestamp) {
    396         mPrefs.edit().putLong(PREF_LAST_PERSONALIZATION_DICT_WIPED_TIME, timestamp).apply();
    397     }
    398 
    399     public long readLastPersonalizationDictGeneratedTime() {
    400         return mPrefs.getLong(PREF_LAST_PERSONALIZATION_DICT_WIPED_TIME, 0);
    401     }
    402 
    403     public void writeCorpusHandlesForPersonalization(final Set<String> corpusHandles) {
    404         mPrefs.edit().putStringSet(PREF_CORPUS_HANDLES_FOR_PERSONALIZATION, corpusHandles).apply();
    405     }
    406 
    407     public Set<String> readCorpusHandlesForPersonalization() {
    408         final Set<String> emptySet = Collections.emptySet();
    409         return mPrefs.getStringSet(PREF_CORPUS_HANDLES_FOR_PERSONALIZATION, emptySet);
    410     }
    411 
    412     public static void writeEmojiRecentKeys(final SharedPreferences prefs, String str) {
    413         prefs.edit().putString(PREF_EMOJI_RECENT_KEYS, str).apply();
    414     }
    415 
    416     public static String readEmojiRecentKeys(final SharedPreferences prefs) {
    417         return prefs.getString(PREF_EMOJI_RECENT_KEYS, "");
    418     }
    419 
    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 
    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 
    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 
    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 
    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