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.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 
    135     public static Settings getInstance() {
    136         return sInstance;
    137     }
    138 
    139     public static void init(final Context context) {
    140         sInstance.onCreate(context);
    141     }
    142 
    143     private Settings() {
    144         // Intentional empty constructor for singleton.
    145     }
    146 
    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 
    154     public void onDestroy() {
    155         mPrefs.unregisterOnSharedPreferenceChangeListener(this);
    156     }
    157 
    158     @Override
    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 
    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.
    193     public SettingsValues getCurrent() {
    194         return mSettingsValues;
    195     }
    196 
    197     public boolean isInternal() {
    198         return mSettingsValues.mIsInternal;
    199     }
    200 
    201     public boolean isWordSeparator(final int code) {
    202         return mSettingsValues.isWordSeparator(code);
    203     }
    204 
    205     public boolean getBlockPotentiallyOffensive() {
    206         return mSettingsValues.mBlockPotentiallyOffensive;
    207     }
    208 
    209     // Accessed from the settings interface, hence public
    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 
    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 
    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 
    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 
    236     public static boolean readFromBuildConfigIfGestureInputEnabled(final Resources res) {
    237         return res.getBoolean(R.bool.config_gesture_input_enabled_by_build_config);
    238     }
    239 
    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 
    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 
    252     public static boolean readFromBuildConfigIfToShowKeyPreviewPopupOption(final Resources res) {
    253         return res.getBoolean(R.bool.config_enable_show_key_preview_popup_option);
    254     }
    255 
    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 
    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 
    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 
    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 
    292     public static void writePrefAdditionalSubtypes(final SharedPreferences prefs,
    293             final String prefSubtypes) {
    294         prefs.edit().putString(PREF_CUSTOM_INPUT_STYLES, prefSubtypes).apply();
    295     }
    296 
    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 
    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 
    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 
    322     public static int readDefaultKeyLongpressTimeout(final Resources res) {
    323         return res.getInteger(R.integer.config_default_longpress_key_timeout);
    324     }
    325 
    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 
    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 
    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 
    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 
    355     public static boolean readUseFullscreenMode(final Resources res) {
    356         return res.getBoolean(R.bool.config_use_fullscreen_mode);
    357     }
    358 
    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 
    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 
    385     public static boolean isInternal(final SharedPreferences prefs) {
    386         return prefs.getBoolean(PREF_KEY_IS_INTERNAL, false);
    387     }
    388 
    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 
    398     public byte[] readLastUsedPersonalizationToken() {
    399         final String tokenStr = mPrefs.getString(PREF_LAST_USED_PERSONALIZATION_TOKEN, null);
    400         return StringUtils.hexStringToByteArray(tokenStr);
    401     }
    402 
    403     public void writeLastPersonalizationDictWipedTime(final long timestamp) {
    404         mPrefs.edit().putLong(PREF_LAST_PERSONALIZATION_DICT_WIPED_TIME, timestamp).apply();
    405     }
    406 
    407     public long readLastPersonalizationDictGeneratedTime() {
    408         return mPrefs.getLong(PREF_LAST_PERSONALIZATION_DICT_WIPED_TIME, 0);
    409     }
    410 
    411     public void writeCorpusHandlesForPersonalization(final Set<String> corpusHandles) {
    412         mPrefs.edit().putStringSet(PREF_CORPUS_HANDLES_FOR_PERSONALIZATION, corpusHandles).apply();
    413     }
    414 
    415     public Set<String> readCorpusHandlesForPersonalization() {
    416         final Set<String> emptySet = Collections.emptySet();
    417         return mPrefs.getStringSet(PREF_CORPUS_HANDLES_FOR_PERSONALIZATION, emptySet);
    418     }
    419 
    420     public static void writeEmojiRecentKeys(final SharedPreferences prefs, String str) {
    421         prefs.edit().putString(PREF_EMOJI_RECENT_KEYS, str).apply();
    422     }
    423 
    424     public static String readEmojiRecentKeys(final SharedPreferences prefs) {
    425         return prefs.getString(PREF_EMOJI_RECENT_KEYS, "");
    426     }
    427 
    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 
    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 
    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 
    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