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.Configuration; 22 import android.content.res.Resources; 23 import android.util.Log; 24 import android.view.inputmethod.EditorInfo; 25 import android.view.inputmethod.InputMethodSubtype; 26 27 import com.android.inputmethod.keyboard.internal.KeySpecParser; 28 import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo; 29 30 import java.util.ArrayList; 31 import java.util.Arrays; 32 import java.util.HashMap; 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 final class SettingsValues { 39 private static final String TAG = SettingsValues.class.getSimpleName(); 40 41 private static final int SUGGESTION_VISIBILITY_SHOW_VALUE 42 = R.string.prefs_suggestion_visibility_show_value; 43 private static final int SUGGESTION_VISIBILITY_SHOW_ONLY_PORTRAIT_VALUE 44 = R.string.prefs_suggestion_visibility_show_only_portrait_value; 45 private static final int SUGGESTION_VISIBILITY_HIDE_VALUE 46 = R.string.prefs_suggestion_visibility_hide_value; 47 48 private static final int[] SUGGESTION_VISIBILITY_VALUE_ARRAY = new int[] { 49 SUGGESTION_VISIBILITY_SHOW_VALUE, 50 SUGGESTION_VISIBILITY_SHOW_ONLY_PORTRAIT_VALUE, 51 SUGGESTION_VISIBILITY_HIDE_VALUE 52 }; 53 54 // From resources: 55 public final int mDelayUpdateOldSuggestions; 56 public final String mWeakSpaceStrippers; 57 public final String mWeakSpaceSwappers; 58 private final String mPhantomSpacePromotingSymbols; 59 public final SuggestedWords mSuggestPuncList; 60 private final String mSymbolsExcludedFromWordSeparators; 61 public final String mWordSeparators; 62 public final CharSequence mHintToSaveText; 63 64 // From preferences, in the same order as xml/prefs.xml: 65 public final boolean mAutoCap; 66 public final boolean mVibrateOn; 67 public final boolean mSoundOn; 68 public final boolean mKeyPreviewPopupOn; 69 private final String mVoiceMode; 70 private final String mAutoCorrectionThresholdRawValue; 71 public final String mShowSuggestionsSetting; 72 @SuppressWarnings("unused") // TODO: Use this 73 private final boolean mUsabilityStudyMode; 74 public final boolean mIncludesOtherImesInLanguageSwitchList; 75 public final boolean mShowsLanguageSwitchKey; 76 @SuppressWarnings("unused") // TODO: Use this 77 private final String mKeyPreviewPopupDismissDelayRawValue; 78 public final boolean mUseContactsDict; 79 // Use bigrams to predict the next word when there is no input for it yet 80 public final boolean mBigramPredictionEnabled; 81 @SuppressWarnings("unused") // TODO: Use this 82 private final int mVibrationDurationSettingsRawValue; 83 @SuppressWarnings("unused") // TODO: Use this 84 private final float mKeypressSoundVolumeRawValue; 85 private final InputMethodSubtype[] mAdditionalSubtypes; 86 public final boolean mGestureInputEnabled; 87 public final boolean mGesturePreviewTrailEnabled; 88 public final boolean mGestureFloatingPreviewTextEnabled; 89 90 // From the input box 91 private final InputAttributes mInputAttributes; 92 93 // Deduced settings 94 public final int mKeypressVibrationDuration; 95 public final float mFxVolume; 96 public final int mKeyPreviewPopupDismissDelay; 97 private final boolean mAutoCorrectEnabled; 98 public final float mAutoCorrectionThreshold; 99 public final boolean mCorrectionEnabled; 100 public final int mSuggestionVisibility; 101 private final boolean mVoiceKeyEnabled; 102 private final boolean mVoiceKeyOnMain; 103 104 public SettingsValues(final SharedPreferences prefs, final InputAttributes inputAttributes, 105 final Context context) { 106 final Resources res = context.getResources(); 107 108 // Get the resources 109 mDelayUpdateOldSuggestions = res.getInteger(R.integer.config_delay_update_old_suggestions); 110 mWeakSpaceStrippers = res.getString(R.string.weak_space_stripping_symbols); 111 mWeakSpaceSwappers = res.getString(R.string.weak_space_swapping_symbols); 112 mPhantomSpacePromotingSymbols = res.getString(R.string.phantom_space_promoting_symbols); 113 if (LatinImeLogger.sDBG) { 114 final int length = mWeakSpaceStrippers.length(); 115 for (int i = 0; i < length; i = mWeakSpaceStrippers.offsetByCodePoints(i, 1)) { 116 if (isWeakSpaceSwapper(mWeakSpaceStrippers.codePointAt(i))) { 117 throw new RuntimeException("Char code " + mWeakSpaceStrippers.codePointAt(i) 118 + " is both a weak space swapper and stripper."); 119 } 120 } 121 } 122 final String[] suggestPuncsSpec = KeySpecParser.parseCsvString( 123 res.getString(R.string.suggested_punctuations), null); 124 mSuggestPuncList = createSuggestPuncList(suggestPuncsSpec); 125 mSymbolsExcludedFromWordSeparators = 126 res.getString(R.string.symbols_excluded_from_word_separators); 127 mWordSeparators = createWordSeparators(mWeakSpaceStrippers, mWeakSpaceSwappers, 128 mSymbolsExcludedFromWordSeparators, res); 129 mHintToSaveText = context.getText(R.string.hint_add_to_dictionary); 130 131 // Store the input attributes 132 if (null == inputAttributes) { 133 mInputAttributes = new InputAttributes(null, false /* isFullscreenMode */); 134 } else { 135 mInputAttributes = inputAttributes; 136 } 137 138 // Get the settings preferences 139 mAutoCap = prefs.getBoolean(Settings.PREF_AUTO_CAP, true); 140 mVibrateOn = isVibrateOn(context, prefs, res); 141 mSoundOn = prefs.getBoolean(Settings.PREF_SOUND_ON, 142 res.getBoolean(R.bool.config_default_sound_enabled)); 143 mKeyPreviewPopupOn = isKeyPreviewPopupEnabled(prefs, res); 144 final String voiceModeMain = res.getString(R.string.voice_mode_main); 145 final String voiceModeOff = res.getString(R.string.voice_mode_off); 146 mVoiceMode = prefs.getString(Settings.PREF_VOICE_MODE, voiceModeMain); 147 mAutoCorrectionThresholdRawValue = prefs.getString(Settings.PREF_AUTO_CORRECTION_THRESHOLD, 148 res.getString(R.string.auto_correction_threshold_mode_index_modest)); 149 mShowSuggestionsSetting = prefs.getString(Settings.PREF_SHOW_SUGGESTIONS_SETTING, 150 res.getString(R.string.prefs_suggestion_visibility_default_value)); 151 mUsabilityStudyMode = getUsabilityStudyMode(prefs); 152 mIncludesOtherImesInLanguageSwitchList = prefs.getBoolean( 153 Settings.PREF_INCLUDE_OTHER_IMES_IN_LANGUAGE_SWITCH_LIST, false); 154 mShowsLanguageSwitchKey = showsLanguageSwitchKey(prefs); 155 mKeyPreviewPopupDismissDelayRawValue = prefs.getString( 156 Settings.PREF_KEY_PREVIEW_POPUP_DISMISS_DELAY, 157 Integer.toString(res.getInteger(R.integer.config_key_preview_linger_timeout))); 158 mUseContactsDict = prefs.getBoolean(Settings.PREF_KEY_USE_CONTACTS_DICT, true); 159 mAutoCorrectEnabled = isAutoCorrectEnabled(res, mAutoCorrectionThresholdRawValue); 160 mBigramPredictionEnabled = isBigramPredictionEnabled(prefs, res); 161 mVibrationDurationSettingsRawValue = 162 prefs.getInt(Settings.PREF_VIBRATION_DURATION_SETTINGS, -1); 163 mKeypressSoundVolumeRawValue = prefs.getFloat(Settings.PREF_KEYPRESS_SOUND_VOLUME, -1.0f); 164 165 // Compute other readable settings 166 mKeypressVibrationDuration = getCurrentVibrationDuration(prefs, res); 167 mFxVolume = getCurrentKeypressSoundVolume(prefs, res); 168 mKeyPreviewPopupDismissDelay = getKeyPreviewPopupDismissDelay(prefs, res); 169 mAutoCorrectionThreshold = getAutoCorrectionThreshold(res, 170 mAutoCorrectionThresholdRawValue); 171 mVoiceKeyEnabled = mVoiceMode != null && !mVoiceMode.equals(voiceModeOff); 172 mVoiceKeyOnMain = mVoiceMode != null && mVoiceMode.equals(voiceModeMain); 173 mAdditionalSubtypes = AdditionalSubtype.createAdditionalSubtypesArray( 174 getPrefAdditionalSubtypes(prefs, res)); 175 final boolean gestureInputEnabledByBuildConfig = res.getBoolean( 176 R.bool.config_gesture_input_enabled_by_build_config); 177 mGestureInputEnabled = gestureInputEnabledByBuildConfig 178 && prefs.getBoolean(Settings.PREF_GESTURE_INPUT, true); 179 mGesturePreviewTrailEnabled = prefs.getBoolean(Settings.PREF_GESTURE_PREVIEW_TRAIL, true); 180 mGestureFloatingPreviewTextEnabled = prefs.getBoolean( 181 Settings.PREF_GESTURE_FLOATING_PREVIEW_TEXT, true); 182 mCorrectionEnabled = mAutoCorrectEnabled && !mInputAttributes.mInputTypeNoAutoCorrect; 183 mSuggestionVisibility = createSuggestionVisibility(res); 184 } 185 186 // Helper functions to create member values. 187 private static SuggestedWords createSuggestPuncList(final String[] puncs) { 188 final ArrayList<SuggestedWordInfo> puncList = CollectionUtils.newArrayList(); 189 if (puncs != null) { 190 for (final String puncSpec : puncs) { 191 puncList.add(new SuggestedWordInfo(KeySpecParser.getLabel(puncSpec), 192 SuggestedWordInfo.MAX_SCORE, SuggestedWordInfo.KIND_HARDCODED, 193 Dictionary.TYPE_HARDCODED)); 194 } 195 } 196 return new SuggestedWords(puncList, 197 false /* typedWordValid */, 198 false /* hasAutoCorrectionCandidate */, 199 true /* isPunctuationSuggestions */, 200 false /* isObsoleteSuggestions */, 201 false /* isPrediction */); 202 } 203 204 private static String createWordSeparators(final String weakSpaceStrippers, 205 final String weakSpaceSwappers, final String symbolsExcludedFromWordSeparators, 206 final Resources res) { 207 String wordSeparators = weakSpaceStrippers + weakSpaceSwappers 208 + res.getString(R.string.phantom_space_promoting_symbols); 209 for (int i = symbolsExcludedFromWordSeparators.length() - 1; i >= 0; --i) { 210 wordSeparators = wordSeparators.replace( 211 symbolsExcludedFromWordSeparators.substring(i, i + 1), ""); 212 } 213 return wordSeparators; 214 } 215 216 private int createSuggestionVisibility(final Resources res) { 217 final String suggestionVisiblityStr = mShowSuggestionsSetting; 218 for (int visibility : SUGGESTION_VISIBILITY_VALUE_ARRAY) { 219 if (suggestionVisiblityStr.equals(res.getString(visibility))) { 220 return visibility; 221 } 222 } 223 throw new RuntimeException("Bug: visibility string is not configured correctly"); 224 } 225 226 private static boolean isVibrateOn(final Context context, final SharedPreferences prefs, 227 final Resources res) { 228 final boolean hasVibrator = VibratorUtils.getInstance(context).hasVibrator(); 229 return hasVibrator && prefs.getBoolean(Settings.PREF_VIBRATE_ON, 230 res.getBoolean(R.bool.config_default_vibration_enabled)); 231 } 232 233 public boolean isApplicationSpecifiedCompletionsOn() { 234 return mInputAttributes.mApplicationSpecifiedCompletionOn; 235 } 236 237 public boolean isSuggestionsRequested(final int displayOrientation) { 238 return mInputAttributes.mIsSettingsSuggestionStripOn 239 && (mCorrectionEnabled 240 || isSuggestionStripVisibleInOrientation(displayOrientation)); 241 } 242 243 public boolean isSuggestionStripVisibleInOrientation(final int orientation) { 244 return (mSuggestionVisibility == SUGGESTION_VISIBILITY_SHOW_VALUE) 245 || (mSuggestionVisibility == SUGGESTION_VISIBILITY_SHOW_ONLY_PORTRAIT_VALUE 246 && orientation == Configuration.ORIENTATION_PORTRAIT); 247 } 248 249 public boolean isWordSeparator(final int code) { 250 return mWordSeparators.contains(String.valueOf((char)code)); 251 } 252 253 public boolean isSymbolExcludedFromWordSeparators(final int code) { 254 return mSymbolsExcludedFromWordSeparators.contains(String.valueOf((char)code)); 255 } 256 257 // TODO: use "Phantom" instead of "Weak" in this method name 258 public boolean isWeakSpaceStripper(final int code) { 259 // TODO: this does not work if the code does not fit in a char 260 return mWeakSpaceStrippers.contains(String.valueOf((char)code)); 261 } 262 263 // TODO: use "Phantom" instead of "Weak" in this method name 264 public boolean isWeakSpaceSwapper(final int code) { 265 // TODO: this does not work if the code does not fit in a char 266 return mWeakSpaceSwappers.contains(String.valueOf((char)code)); 267 } 268 269 public boolean isPhantomSpacePromotingSymbol(final int code) { 270 // TODO: this does not work if the code does not fit in a char 271 return mPhantomSpacePromotingSymbols.contains(String.valueOf((char)code)); 272 } 273 274 private static boolean isAutoCorrectEnabled(final Resources res, 275 final String currentAutoCorrectionSetting) { 276 final String autoCorrectionOff = res.getString( 277 R.string.auto_correction_threshold_mode_index_off); 278 return !currentAutoCorrectionSetting.equals(autoCorrectionOff); 279 } 280 281 // Public to access from KeyboardSwitcher. Should it have access to some 282 // process-global instance instead? 283 public static boolean isKeyPreviewPopupEnabled(final SharedPreferences prefs, 284 final Resources res) { 285 final boolean showPopupOption = res.getBoolean( 286 R.bool.config_enable_show_popup_on_keypress_option); 287 if (!showPopupOption) return res.getBoolean(R.bool.config_default_popup_preview); 288 return prefs.getBoolean(Settings.PREF_POPUP_ON, 289 res.getBoolean(R.bool.config_default_popup_preview)); 290 } 291 292 // Likewise 293 public static int getKeyPreviewPopupDismissDelay(final SharedPreferences prefs, 294 final Resources res) { 295 // TODO: use mKeyPreviewPopupDismissDelayRawValue instead of reading it again here. 296 return Integer.parseInt(prefs.getString(Settings.PREF_KEY_PREVIEW_POPUP_DISMISS_DELAY, 297 Integer.toString(res.getInteger( 298 R.integer.config_key_preview_linger_timeout)))); 299 } 300 301 private static boolean isBigramPredictionEnabled(final SharedPreferences prefs, 302 final Resources res) { 303 return prefs.getBoolean(Settings.PREF_BIGRAM_PREDICTIONS, res.getBoolean( 304 R.bool.config_default_next_word_prediction)); 305 } 306 307 private static float getAutoCorrectionThreshold(final Resources res, 308 final String currentAutoCorrectionSetting) { 309 final String[] autoCorrectionThresholdValues = res.getStringArray( 310 R.array.auto_correction_threshold_values); 311 // When autoCorrectionThreshold is greater than 1.0, it's like auto correction is off. 312 float autoCorrectionThreshold = Float.MAX_VALUE; 313 try { 314 final int arrayIndex = Integer.valueOf(currentAutoCorrectionSetting); 315 if (arrayIndex >= 0 && arrayIndex < autoCorrectionThresholdValues.length) { 316 autoCorrectionThreshold = Float.parseFloat( 317 autoCorrectionThresholdValues[arrayIndex]); 318 } 319 } catch (NumberFormatException e) { 320 // Whenever the threshold settings are correct, never come here. 321 autoCorrectionThreshold = Float.MAX_VALUE; 322 Log.w(TAG, "Cannot load auto correction threshold setting." 323 + " currentAutoCorrectionSetting: " + currentAutoCorrectionSetting 324 + ", autoCorrectionThresholdValues: " 325 + Arrays.toString(autoCorrectionThresholdValues)); 326 } 327 return autoCorrectionThreshold; 328 } 329 330 public boolean isVoiceKeyEnabled(final EditorInfo editorInfo) { 331 final boolean shortcutImeEnabled = SubtypeSwitcher.getInstance().isShortcutImeEnabled(); 332 final int inputType = (editorInfo != null) ? editorInfo.inputType : 0; 333 return shortcutImeEnabled && mVoiceKeyEnabled 334 && !InputTypeUtils.isPasswordInputType(inputType); 335 } 336 337 public boolean isVoiceKeyOnMain() { 338 return mVoiceKeyOnMain; 339 } 340 341 // This preference key is deprecated. Use {@link #PREF_SHOW_LANGUAGE_SWITCH_KEY} instead. 342 // This is being used only for the backward compatibility. 343 private static final String PREF_SUPPRESS_LANGUAGE_SWITCH_KEY = 344 "pref_suppress_language_switch_key"; 345 346 public static boolean showsLanguageSwitchKey(final SharedPreferences prefs) { 347 if (prefs.contains(PREF_SUPPRESS_LANGUAGE_SWITCH_KEY)) { 348 final boolean suppressLanguageSwitchKey = prefs.getBoolean( 349 PREF_SUPPRESS_LANGUAGE_SWITCH_KEY, false); 350 final SharedPreferences.Editor editor = prefs.edit(); 351 editor.remove(PREF_SUPPRESS_LANGUAGE_SWITCH_KEY); 352 editor.putBoolean(Settings.PREF_SHOW_LANGUAGE_SWITCH_KEY, !suppressLanguageSwitchKey); 353 editor.apply(); 354 } 355 return prefs.getBoolean(Settings.PREF_SHOW_LANGUAGE_SWITCH_KEY, true); 356 } 357 358 public boolean isLanguageSwitchKeyEnabled(final Context context) { 359 if (!mShowsLanguageSwitchKey) { 360 return false; 361 } 362 if (mIncludesOtherImesInLanguageSwitchList) { 363 return ImfUtils.hasMultipleEnabledIMEsOrSubtypes( 364 context, /* include aux subtypes */false); 365 } else { 366 return ImfUtils.hasMultipleEnabledSubtypesInThisIme( 367 context, /* include aux subtypes */false); 368 } 369 } 370 371 public static boolean isFullscreenModeAllowed(final Resources res) { 372 return res.getBoolean(R.bool.config_use_fullscreen_mode); 373 } 374 375 public InputMethodSubtype[] getAdditionalSubtypes() { 376 return mAdditionalSubtypes; 377 } 378 379 public static String getPrefAdditionalSubtypes(final SharedPreferences prefs, 380 final Resources res) { 381 final String predefinedPrefSubtypes = AdditionalSubtype.createPrefSubtypes( 382 res.getStringArray(R.array.predefined_subtypes)); 383 return prefs.getString(Settings.PREF_CUSTOM_INPUT_STYLES, predefinedPrefSubtypes); 384 } 385 386 // Accessed from the settings interface, hence public 387 public static float getCurrentKeypressSoundVolume(final SharedPreferences prefs, 388 final Resources res) { 389 // TODO: use mVibrationDurationSettingsRawValue instead of reading it again here 390 final float volume = prefs.getFloat(Settings.PREF_KEYPRESS_SOUND_VOLUME, -1.0f); 391 if (volume >= 0) { 392 return volume; 393 } 394 395 return Float.parseFloat(ResourceUtils.getDeviceOverrideValue( 396 res, R.array.keypress_volumes, "-1.0f")); 397 } 398 399 // Likewise 400 public static int getCurrentVibrationDuration(final SharedPreferences prefs, 401 final Resources res) { 402 // TODO: use mKeypressVibrationDuration instead of reading it again here 403 final int ms = prefs.getInt(Settings.PREF_VIBRATION_DURATION_SETTINGS, -1); 404 if (ms >= 0) { 405 return ms; 406 } 407 408 return Integer.parseInt(ResourceUtils.getDeviceOverrideValue( 409 res, R.array.keypress_vibration_durations, "-1")); 410 } 411 412 // Likewise 413 public static boolean getUsabilityStudyMode(final SharedPreferences prefs) { 414 // TODO: use mUsabilityStudyMode instead of reading it again here 415 return prefs.getBoolean(DebugSettings.PREF_USABILITY_STUDY_MODE, true); 416 } 417 418 public static long getLastUserHistoryWriteTime(final SharedPreferences prefs, 419 final String locale) { 420 final String str = prefs.getString(Settings.PREF_LAST_USER_DICTIONARY_WRITE_TIME, ""); 421 final HashMap<String, Long> map = LocaleUtils.localeAndTimeStrToHashMap(str); 422 if (map.containsKey(locale)) { 423 return map.get(locale); 424 } 425 return 0; 426 } 427 428 public static void setLastUserHistoryWriteTime(final SharedPreferences prefs, 429 final String locale) { 430 final String oldStr = prefs.getString(Settings.PREF_LAST_USER_DICTIONARY_WRITE_TIME, ""); 431 final HashMap<String, Long> map = LocaleUtils.localeAndTimeStrToHashMap(oldStr); 432 map.put(locale, System.currentTimeMillis()); 433 final String newStr = LocaleUtils.localeAndTimeHashMapToStr(map); 434 prefs.edit().putString(Settings.PREF_LAST_USER_DICTIONARY_WRITE_TIME, newStr).apply(); 435 } 436 437 public boolean isSameInputType(final EditorInfo editorInfo) { 438 return mInputAttributes.isSameInputType(editorInfo); 439 } 440 441 // For debug. 442 public String getInputAttributesDebugString() { 443 return mInputAttributes.toString(); 444 } 445 } 446