1 /* 2 * Copyright (C) 2011 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 * use this file except in compliance with the License. You may obtain a copy of 6 * 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, WITHOUT 12 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 * License for the specific language governing permissions and limitations under 14 * the License. 15 */ 16 17 package com.android.inputmethod.latin; 18 19 import android.content.Context; 20 import android.content.SharedPreferences; 21 import android.content.res.Resources; 22 import android.util.Log; 23 import android.view.inputmethod.EditorInfo; 24 import android.view.inputmethod.InputMethodSubtype; 25 26 import com.android.inputmethod.keyboard.internal.KeySpecParser; 27 import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo; 28 29 import java.util.ArrayList; 30 import java.util.Arrays; 31 import java.util.HashMap; 32 import java.util.Map; 33 34 /** 35 * When you call the constructor of this class, you may want to change the current system locale by 36 * using {@link LocaleUtils.RunInLocale}. 37 */ 38 public class SettingsValues { 39 private static final String TAG = SettingsValues.class.getSimpleName(); 40 41 // From resources: 42 public final int mDelayUpdateOldSuggestions; 43 public final String mWeakSpaceStrippers; 44 public final String mWeakSpaceSwappers; 45 private final String mPhantomSpacePromotingSymbols; 46 public final SuggestedWords mSuggestPuncList; 47 private final String mSymbolsExcludedFromWordSeparators; 48 public final String mWordSeparators; 49 public final CharSequence mHintToSaveText; 50 51 // From preferences, in the same order as xml/prefs.xml: 52 public final boolean mAutoCap; 53 public final boolean mVibrateOn; 54 public final boolean mSoundOn; 55 public final boolean mKeyPreviewPopupOn; 56 private final String mVoiceMode; 57 private final String mAutoCorrectionThresholdRawValue; 58 public final String mShowSuggestionsSetting; 59 @SuppressWarnings("unused") // TODO: Use this 60 private final boolean mUsabilityStudyMode; 61 public final boolean mIncludesOtherImesInLanguageSwitchList; 62 public final boolean mIsLanguageSwitchKeySuppressed; 63 @SuppressWarnings("unused") // TODO: Use this 64 private final String mKeyPreviewPopupDismissDelayRawValue; 65 public final boolean mUseContactsDict; 66 // Suggestion: use bigrams to adjust scores of suggestions obtained from unigram dictionary 67 public final boolean mBigramSuggestionEnabled; 68 // Prediction: use bigrams to predict the next word when there is no input for it yet 69 public final boolean mBigramPredictionEnabled; 70 public final boolean mEnableSuggestionSpanInsertion; 71 @SuppressWarnings("unused") // TODO: Use this 72 private final int mVibrationDurationSettingsRawValue; 73 @SuppressWarnings("unused") // TODO: Use this 74 private final float mKeypressSoundVolumeRawValue; 75 private final InputMethodSubtype[] mAdditionalSubtypes; 76 77 // Deduced settings 78 public final int mKeypressVibrationDuration; 79 public final float mFxVolume; 80 public final int mKeyPreviewPopupDismissDelay; 81 public final boolean mAutoCorrectEnabled; 82 public final float mAutoCorrectionThreshold; 83 private final boolean mVoiceKeyEnabled; 84 private final boolean mVoiceKeyOnMain; 85 86 public SettingsValues(final SharedPreferences prefs, final Context context) { 87 final Resources res = context.getResources(); 88 89 // Get the resources 90 mDelayUpdateOldSuggestions = res.getInteger(R.integer.config_delay_update_old_suggestions); 91 mWeakSpaceStrippers = res.getString(R.string.weak_space_stripping_symbols); 92 mWeakSpaceSwappers = res.getString(R.string.weak_space_swapping_symbols); 93 mPhantomSpacePromotingSymbols = res.getString(R.string.phantom_space_promoting_symbols); 94 if (LatinImeLogger.sDBG) { 95 final int length = mWeakSpaceStrippers.length(); 96 for (int i = 0; i < length; i = mWeakSpaceStrippers.offsetByCodePoints(i, 1)) { 97 if (isWeakSpaceSwapper(mWeakSpaceStrippers.codePointAt(i))) { 98 throw new RuntimeException("Char code " + mWeakSpaceStrippers.codePointAt(i) 99 + " is both a weak space swapper and stripper."); 100 } 101 } 102 } 103 final String[] suggestPuncsSpec = KeySpecParser.parseCsvString( 104 res.getString(R.string.suggested_punctuations), null); 105 mSuggestPuncList = createSuggestPuncList(suggestPuncsSpec); 106 mSymbolsExcludedFromWordSeparators = 107 res.getString(R.string.symbols_excluded_from_word_separators); 108 mWordSeparators = createWordSeparators(mWeakSpaceStrippers, mWeakSpaceSwappers, 109 mSymbolsExcludedFromWordSeparators, res); 110 mHintToSaveText = context.getText(R.string.hint_add_to_dictionary); 111 112 // Get the settings preferences 113 mAutoCap = prefs.getBoolean(Settings.PREF_AUTO_CAP, true); 114 mVibrateOn = isVibrateOn(context, prefs, res); 115 mSoundOn = prefs.getBoolean(Settings.PREF_SOUND_ON, 116 res.getBoolean(R.bool.config_default_sound_enabled)); 117 mKeyPreviewPopupOn = isKeyPreviewPopupEnabled(prefs, res); 118 final String voiceModeMain = res.getString(R.string.voice_mode_main); 119 final String voiceModeOff = res.getString(R.string.voice_mode_off); 120 mVoiceMode = prefs.getString(Settings.PREF_VOICE_MODE, voiceModeMain); 121 mAutoCorrectionThresholdRawValue = prefs.getString(Settings.PREF_AUTO_CORRECTION_THRESHOLD, 122 res.getString(R.string.auto_correction_threshold_mode_index_modest)); 123 mShowSuggestionsSetting = prefs.getString(Settings.PREF_SHOW_SUGGESTIONS_SETTING, 124 res.getString(R.string.prefs_suggestion_visibility_default_value)); 125 mUsabilityStudyMode = getUsabilityStudyMode(prefs); 126 mIncludesOtherImesInLanguageSwitchList = prefs.getBoolean( 127 Settings.PREF_INCLUDE_OTHER_IMES_IN_LANGUAGE_SWITCH_LIST, false); 128 mIsLanguageSwitchKeySuppressed = isLanguageSwitchKeySupressed(prefs); 129 mKeyPreviewPopupDismissDelayRawValue = prefs.getString( 130 Settings.PREF_KEY_PREVIEW_POPUP_DISMISS_DELAY, 131 Integer.toString(res.getInteger(R.integer.config_key_preview_linger_timeout))); 132 mUseContactsDict = prefs.getBoolean(Settings.PREF_KEY_USE_CONTACTS_DICT, true); 133 mAutoCorrectEnabled = isAutoCorrectEnabled(res, mAutoCorrectionThresholdRawValue); 134 mBigramSuggestionEnabled = mAutoCorrectEnabled 135 && isBigramSuggestionEnabled(prefs, res, mAutoCorrectEnabled); 136 mBigramPredictionEnabled = mBigramSuggestionEnabled 137 && isBigramPredictionEnabled(prefs, res); 138 // TODO: remove mEnableSuggestionSpanInsertion. It's always true. 139 mEnableSuggestionSpanInsertion = true; 140 mVibrationDurationSettingsRawValue = 141 prefs.getInt(Settings.PREF_VIBRATION_DURATION_SETTINGS, -1); 142 mKeypressSoundVolumeRawValue = prefs.getFloat(Settings.PREF_KEYPRESS_SOUND_VOLUME, -1.0f); 143 144 // Compute other readable settings 145 mKeypressVibrationDuration = getCurrentVibrationDuration(prefs, res); 146 mFxVolume = getCurrentKeypressSoundVolume(prefs, res); 147 mKeyPreviewPopupDismissDelay = getKeyPreviewPopupDismissDelay(prefs, res); 148 mAutoCorrectionThreshold = getAutoCorrectionThreshold(res, 149 mAutoCorrectionThresholdRawValue); 150 mVoiceKeyEnabled = mVoiceMode != null && !mVoiceMode.equals(voiceModeOff); 151 mVoiceKeyOnMain = mVoiceMode != null && mVoiceMode.equals(voiceModeMain); 152 mAdditionalSubtypes = AdditionalSubtype.createAdditionalSubtypesArray( 153 getPrefAdditionalSubtypes(prefs, res)); 154 } 155 156 // Helper functions to create member values. 157 private static SuggestedWords createSuggestPuncList(final String[] puncs) { 158 final ArrayList<SuggestedWords.SuggestedWordInfo> puncList = 159 new ArrayList<SuggestedWords.SuggestedWordInfo>(); 160 if (puncs != null) { 161 for (final String puncSpec : puncs) { 162 puncList.add(new SuggestedWords.SuggestedWordInfo( 163 KeySpecParser.getLabel(puncSpec), SuggestedWordInfo.MAX_SCORE)); 164 } 165 } 166 return new SuggestedWords(puncList, 167 false /* typedWordValid */, 168 false /* hasAutoCorrectionCandidate */, 169 false /* allowsToBeAutoCorrected */, 170 true /* isPunctuationSuggestions */, 171 false /* isObsoleteSuggestions */, 172 false /* isPrediction */); 173 } 174 175 private static String createWordSeparators(final String weakSpaceStrippers, 176 final String weakSpaceSwappers, final String symbolsExcludedFromWordSeparators, 177 final Resources res) { 178 String wordSeparators = weakSpaceStrippers + weakSpaceSwappers 179 + res.getString(R.string.phantom_space_promoting_symbols); 180 for (int i = symbolsExcludedFromWordSeparators.length() - 1; i >= 0; --i) { 181 wordSeparators = wordSeparators.replace( 182 symbolsExcludedFromWordSeparators.substring(i, i + 1), ""); 183 } 184 return wordSeparators; 185 } 186 187 private static boolean isVibrateOn(final Context context, final SharedPreferences prefs, 188 final Resources res) { 189 final boolean hasVibrator = VibratorUtils.getInstance(context).hasVibrator(); 190 return hasVibrator && prefs.getBoolean(Settings.PREF_VIBRATE_ON, 191 res.getBoolean(R.bool.config_default_vibration_enabled)); 192 } 193 194 public boolean isWordSeparator(int code) { 195 return mWordSeparators.contains(String.valueOf((char)code)); 196 } 197 198 public boolean isSymbolExcludedFromWordSeparators(int code) { 199 return mSymbolsExcludedFromWordSeparators.contains(String.valueOf((char)code)); 200 } 201 202 public boolean isWeakSpaceStripper(int code) { 203 // TODO: this does not work if the code does not fit in a char 204 return mWeakSpaceStrippers.contains(String.valueOf((char)code)); 205 } 206 207 public boolean isWeakSpaceSwapper(int code) { 208 // TODO: this does not work if the code does not fit in a char 209 return mWeakSpaceSwappers.contains(String.valueOf((char)code)); 210 } 211 212 public boolean isPhantomSpacePromotingSymbol(int code) { 213 // TODO: this does not work if the code does not fit in a char 214 return mPhantomSpacePromotingSymbols.contains(String.valueOf((char)code)); 215 } 216 217 private static boolean isAutoCorrectEnabled(final Resources resources, 218 final String currentAutoCorrectionSetting) { 219 final String autoCorrectionOff = resources.getString( 220 R.string.auto_correction_threshold_mode_index_off); 221 return !currentAutoCorrectionSetting.equals(autoCorrectionOff); 222 } 223 224 // Public to access from KeyboardSwitcher. Should it have access to some 225 // process-global instance instead? 226 public static boolean isKeyPreviewPopupEnabled(SharedPreferences sp, Resources resources) { 227 final boolean showPopupOption = resources.getBoolean( 228 R.bool.config_enable_show_popup_on_keypress_option); 229 if (!showPopupOption) return resources.getBoolean(R.bool.config_default_popup_preview); 230 return sp.getBoolean(Settings.PREF_POPUP_ON, 231 resources.getBoolean(R.bool.config_default_popup_preview)); 232 } 233 234 // Likewise 235 public static int getKeyPreviewPopupDismissDelay(SharedPreferences sp, 236 Resources resources) { 237 // TODO: use mKeyPreviewPopupDismissDelayRawValue instead of reading it again here. 238 return Integer.parseInt(sp.getString(Settings.PREF_KEY_PREVIEW_POPUP_DISMISS_DELAY, 239 Integer.toString(resources.getInteger( 240 R.integer.config_key_preview_linger_timeout)))); 241 } 242 243 private static boolean isBigramSuggestionEnabled(final SharedPreferences sp, 244 final Resources resources, final boolean autoCorrectEnabled) { 245 // TODO: remove this method. Bigram suggestion is always true. 246 return true; 247 } 248 249 private static boolean isBigramPredictionEnabled(final SharedPreferences sp, 250 final Resources resources) { 251 return sp.getBoolean(Settings.PREF_BIGRAM_PREDICTIONS, resources.getBoolean( 252 R.bool.config_default_next_word_prediction)); 253 } 254 255 private static float getAutoCorrectionThreshold(final Resources resources, 256 final String currentAutoCorrectionSetting) { 257 final String[] autoCorrectionThresholdValues = resources.getStringArray( 258 R.array.auto_correction_threshold_values); 259 // When autoCorrectionThreshold is greater than 1.0, it's like auto correction is off. 260 float autoCorrectionThreshold = Float.MAX_VALUE; 261 try { 262 final int arrayIndex = Integer.valueOf(currentAutoCorrectionSetting); 263 if (arrayIndex >= 0 && arrayIndex < autoCorrectionThresholdValues.length) { 264 autoCorrectionThreshold = Float.parseFloat( 265 autoCorrectionThresholdValues[arrayIndex]); 266 } 267 } catch (NumberFormatException e) { 268 // Whenever the threshold settings are correct, never come here. 269 autoCorrectionThreshold = Float.MAX_VALUE; 270 Log.w(TAG, "Cannot load auto correction threshold setting." 271 + " currentAutoCorrectionSetting: " + currentAutoCorrectionSetting 272 + ", autoCorrectionThresholdValues: " 273 + Arrays.toString(autoCorrectionThresholdValues)); 274 } 275 return autoCorrectionThreshold; 276 } 277 278 public boolean isVoiceKeyEnabled(final EditorInfo editorInfo) { 279 final boolean shortcutImeEnabled = SubtypeSwitcher.getInstance().isShortcutImeEnabled(); 280 final int inputType = (editorInfo != null) ? editorInfo.inputType : 0; 281 return shortcutImeEnabled && mVoiceKeyEnabled 282 && !InputTypeUtils.isPasswordInputType(inputType); 283 } 284 285 public boolean isVoiceKeyOnMain() { 286 return mVoiceKeyOnMain; 287 } 288 289 public static boolean isLanguageSwitchKeySupressed(SharedPreferences sp) { 290 return sp.getBoolean(Settings.PREF_SUPPRESS_LANGUAGE_SWITCH_KEY, false); 291 } 292 293 public boolean isLanguageSwitchKeyEnabled(Context context) { 294 if (mIsLanguageSwitchKeySuppressed) { 295 return false; 296 } 297 if (mIncludesOtherImesInLanguageSwitchList) { 298 return ImfUtils.hasMultipleEnabledIMEsOrSubtypes( 299 context, /* include aux subtypes */false); 300 } else { 301 return ImfUtils.hasMultipleEnabledSubtypesInThisIme( 302 context, /* include aux subtypes */false); 303 } 304 } 305 306 public boolean isFullscreenModeAllowed(Resources res) { 307 return res.getBoolean(R.bool.config_use_fullscreen_mode); 308 } 309 310 public InputMethodSubtype[] getAdditionalSubtypes() { 311 return mAdditionalSubtypes; 312 } 313 314 public static String getPrefAdditionalSubtypes(final SharedPreferences prefs, 315 final Resources res) { 316 final String prefSubtypes = res.getString(R.string.predefined_subtypes, ""); 317 return prefs.getString(Settings.PREF_CUSTOM_INPUT_STYLES, prefSubtypes); 318 } 319 320 // Accessed from the settings interface, hence public 321 public static float getCurrentKeypressSoundVolume(final SharedPreferences sp, 322 final Resources res) { 323 // TODO: use mVibrationDurationSettingsRawValue instead of reading it again here 324 final float volume = sp.getFloat(Settings.PREF_KEYPRESS_SOUND_VOLUME, -1.0f); 325 if (volume >= 0) { 326 return volume; 327 } 328 329 return Float.parseFloat( 330 Utils.getDeviceOverrideValue(res, R.array.keypress_volumes, "-1.0f")); 331 } 332 333 // Likewise 334 public static int getCurrentVibrationDuration(final SharedPreferences sp, 335 final Resources res) { 336 // TODO: use mKeypressVibrationDuration instead of reading it again here 337 final int ms = sp.getInt(Settings.PREF_VIBRATION_DURATION_SETTINGS, -1); 338 if (ms >= 0) { 339 return ms; 340 } 341 342 return Integer.parseInt( 343 Utils.getDeviceOverrideValue(res, R.array.keypress_vibration_durations, "-1")); 344 } 345 346 // Likewise 347 public static boolean getUsabilityStudyMode(final SharedPreferences prefs) { 348 // TODO: use mUsabilityStudyMode instead of reading it again here 349 return prefs.getBoolean(Settings.PREF_USABILITY_STUDY_MODE, true); 350 } 351 352 public static long getLastUserHistoryWriteTime( 353 final SharedPreferences prefs, final String locale) { 354 final String str = prefs.getString(Settings.PREF_LAST_USER_DICTIONARY_WRITE_TIME, ""); 355 final HashMap<String, Long> map = Utils.localeAndTimeStrToHashMap(str); 356 if (map.containsKey(locale)) { 357 return map.get(locale); 358 } 359 return 0; 360 } 361 362 public static void setLastUserHistoryWriteTime( 363 final SharedPreferences prefs, final String locale) { 364 final String oldStr = prefs.getString(Settings.PREF_LAST_USER_DICTIONARY_WRITE_TIME, ""); 365 final HashMap<String, Long> map = Utils.localeAndTimeStrToHashMap(oldStr); 366 map.put(locale, System.currentTimeMillis()); 367 final String newStr = Utils.localeAndTimeHashMapToStr(map); 368 prefs.edit().putString(Settings.PREF_LAST_USER_DICTIONARY_WRITE_TIME, newStr).apply(); 369 } 370 } 371