1/* 2 * Copyright (C) 2011 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 17package com.android.inputmethod.latin.settings; 18 19import android.content.SharedPreferences; 20import android.content.res.Configuration; 21import android.content.res.Resources; 22import android.util.Log; 23import android.view.inputmethod.EditorInfo; 24 25import com.android.inputmethod.annotations.UsedForTesting; 26import com.android.inputmethod.keyboard.internal.KeySpecParser; 27import com.android.inputmethod.latin.Constants; 28import com.android.inputmethod.latin.Dictionary; 29import com.android.inputmethod.latin.InputAttributes; 30import com.android.inputmethod.latin.R; 31import com.android.inputmethod.latin.RichInputMethodManager; 32import com.android.inputmethod.latin.SubtypeSwitcher; 33import com.android.inputmethod.latin.SuggestedWords; 34import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo; 35import com.android.inputmethod.latin.utils.CollectionUtils; 36import com.android.inputmethod.latin.utils.InputTypeUtils; 37import com.android.inputmethod.latin.utils.StringUtils; 38 39import java.util.ArrayList; 40import java.util.Arrays; 41import java.util.Locale; 42 43/** 44 * When you call the constructor of this class, you may want to change the current system locale by 45 * using {@link com.android.inputmethod.latin.utils.RunInLocale}. 46 */ 47public final class SettingsValues { 48 private static final String TAG = SettingsValues.class.getSimpleName(); 49 // "floatMaxValue" and "floatNegativeInfinity" are special marker strings for 50 // Float.NEGATIVE_INFINITE and Float.MAX_VALUE. Currently used for auto-correction settings. 51 private static final String FLOAT_MAX_VALUE_MARKER_STRING = "floatMaxValue"; 52 private static final String FLOAT_NEGATIVE_INFINITY_MARKER_STRING = "floatNegativeInfinity"; 53 54 // From resources: 55 public final int mDelayUpdateOldSuggestions; 56 public final int[] mSymbolsPrecededBySpace; 57 public final int[] mSymbolsFollowedBySpace; 58 public final int[] mWordConnectors; 59 public final SuggestedWords mSuggestPuncList; 60 public final String mWordSeparators; 61 public final int mSentenceSeparator; 62 public final CharSequence mHintToSaveText; 63 public final boolean mCurrentLanguageHasSpaces; 64 65 // From preferences, in the same order as xml/prefs.xml: 66 public final boolean mAutoCap; 67 public final boolean mVibrateOn; 68 public final boolean mSoundOn; 69 public final boolean mKeyPreviewPopupOn; 70 private final boolean mShowsVoiceInputKey; 71 public final boolean mIncludesOtherImesInLanguageSwitchList; 72 public final boolean mShowsLanguageSwitchKey; 73 public final boolean mUseContactsDict; 74 public final boolean mUseDoubleSpacePeriod; 75 public final boolean mBlockPotentiallyOffensive; 76 // Use bigrams to predict the next word when there is no input for it yet 77 public final boolean mBigramPredictionEnabled; 78 public final boolean mGestureInputEnabled; 79 public final boolean mGestureTrailEnabled; 80 public final boolean mGestureFloatingPreviewTextEnabled; 81 public final boolean mSlidingKeyInputPreviewEnabled; 82 public final boolean mPhraseGestureEnabled; 83 public final int mKeyLongpressTimeout; 84 public final Locale mLocale; 85 86 // From the input box 87 public final InputAttributes mInputAttributes; 88 89 // Deduced settings 90 public final int mKeypressVibrationDuration; 91 public final float mKeypressSoundVolume; 92 public final int mKeyPreviewPopupDismissDelay; 93 private final boolean mAutoCorrectEnabled; 94 public final float mAutoCorrectionThreshold; 95 public final boolean mCorrectionEnabled; 96 public final int mSuggestionVisibility; 97 public final boolean mBoostPersonalizationDictionaryForDebug; 98 public final boolean mUseOnlyPersonalizationDictionaryForDebug; 99 100 // Setting values for additional features 101 public final int[] mAdditionalFeaturesSettingValues = 102 new int[AdditionalFeaturesSettingUtils.ADDITIONAL_FEATURES_SETTINGS_SIZE]; 103 104 // Debug settings 105 public final boolean mIsInternal; 106 107 public SettingsValues(final SharedPreferences prefs, final Locale locale, final Resources res, 108 final InputAttributes inputAttributes) { 109 mLocale = locale; 110 // Get the resources 111 mDelayUpdateOldSuggestions = res.getInteger(R.integer.config_delay_update_old_suggestions); 112 mSymbolsPrecededBySpace = 113 StringUtils.toCodePointArray(res.getString(R.string.symbols_preceded_by_space)); 114 Arrays.sort(mSymbolsPrecededBySpace); 115 mSymbolsFollowedBySpace = 116 StringUtils.toCodePointArray(res.getString(R.string.symbols_followed_by_space)); 117 Arrays.sort(mSymbolsFollowedBySpace); 118 mWordConnectors = 119 StringUtils.toCodePointArray(res.getString(R.string.symbols_word_connectors)); 120 Arrays.sort(mWordConnectors); 121 final String[] suggestPuncsSpec = KeySpecParser.splitKeySpecs(res.getString( 122 R.string.suggested_punctuations)); 123 mSuggestPuncList = createSuggestPuncList(suggestPuncsSpec); 124 mWordSeparators = res.getString(R.string.symbols_word_separators); 125 mSentenceSeparator = res.getInteger(R.integer.sentence_separator); 126 mHintToSaveText = res.getText(R.string.hint_add_to_dictionary); 127 mCurrentLanguageHasSpaces = res.getBoolean(R.bool.current_language_has_spaces); 128 129 // Store the input attributes 130 if (null == inputAttributes) { 131 mInputAttributes = new InputAttributes(null, false /* isFullscreenMode */); 132 } else { 133 mInputAttributes = inputAttributes; 134 } 135 136 // Get the settings preferences 137 mAutoCap = prefs.getBoolean(Settings.PREF_AUTO_CAP, true); 138 mVibrateOn = Settings.readVibrationEnabled(prefs, res); 139 mSoundOn = Settings.readKeypressSoundEnabled(prefs, res); 140 mKeyPreviewPopupOn = Settings.readKeyPreviewPopupEnabled(prefs, res); 141 mSlidingKeyInputPreviewEnabled = prefs.getBoolean( 142 Settings.PREF_SLIDING_KEY_INPUT_PREVIEW, true); 143 mShowsVoiceInputKey = needsToShowVoiceInputKey(prefs, res); 144 final String autoCorrectionThresholdRawValue = prefs.getString( 145 Settings.PREF_AUTO_CORRECTION_THRESHOLD, 146 res.getString(R.string.auto_correction_threshold_mode_index_modest)); 147 mIncludesOtherImesInLanguageSwitchList = prefs.getBoolean( 148 Settings.PREF_INCLUDE_OTHER_IMES_IN_LANGUAGE_SWITCH_LIST, false); 149 mShowsLanguageSwitchKey = Settings.readShowsLanguageSwitchKey(prefs); 150 mUseContactsDict = prefs.getBoolean(Settings.PREF_KEY_USE_CONTACTS_DICT, true); 151 mUseDoubleSpacePeriod = prefs.getBoolean(Settings.PREF_KEY_USE_DOUBLE_SPACE_PERIOD, true); 152 mBlockPotentiallyOffensive = Settings.readBlockPotentiallyOffensive(prefs, res); 153 mAutoCorrectEnabled = Settings.readAutoCorrectEnabled(autoCorrectionThresholdRawValue, res); 154 mBigramPredictionEnabled = readBigramPredictionEnabled(prefs, res); 155 156 // Compute other readable settings 157 mKeyLongpressTimeout = Settings.readKeyLongpressTimeout(prefs, res); 158 mKeypressVibrationDuration = Settings.readKeypressVibrationDuration(prefs, res); 159 mKeypressSoundVolume = Settings.readKeypressSoundVolume(prefs, res); 160 mKeyPreviewPopupDismissDelay = Settings.readKeyPreviewPopupDismissDelay(prefs, res); 161 mAutoCorrectionThreshold = readAutoCorrectionThreshold(res, 162 autoCorrectionThresholdRawValue); 163 mGestureInputEnabled = Settings.readGestureInputEnabled(prefs, res); 164 mGestureTrailEnabled = prefs.getBoolean(Settings.PREF_GESTURE_PREVIEW_TRAIL, true); 165 mGestureFloatingPreviewTextEnabled = prefs.getBoolean( 166 Settings.PREF_GESTURE_FLOATING_PREVIEW_TEXT, true); 167 mPhraseGestureEnabled = Settings.readPhraseGestureEnabled(prefs, res); 168 mCorrectionEnabled = mAutoCorrectEnabled && !mInputAttributes.mInputTypeNoAutoCorrect; 169 final String showSuggestionsSetting = prefs.getString( 170 Settings.PREF_SHOW_SUGGESTIONS_SETTING, 171 res.getString(R.string.prefs_suggestion_visibility_default_value)); 172 mSuggestionVisibility = createSuggestionVisibility(res, showSuggestionsSetting); 173 AdditionalFeaturesSettingUtils.readAdditionalFeaturesPreferencesIntoArray( 174 prefs, mAdditionalFeaturesSettingValues); 175 mIsInternal = Settings.isInternal(prefs); 176 mBoostPersonalizationDictionaryForDebug = 177 Settings.readBoostPersonalizationDictionaryForDebug(prefs); 178 mUseOnlyPersonalizationDictionaryForDebug = 179 Settings.readUseOnlyPersonalizationDictionaryForDebug(prefs); 180 } 181 182 // Only for tests 183 private SettingsValues(final Locale locale) { 184 // TODO: locale is saved, but not used yet. May have to change this if tests require. 185 mLocale = locale; 186 mDelayUpdateOldSuggestions = 0; 187 mSymbolsPrecededBySpace = new int[] { '(', '[', '{', '&' }; 188 Arrays.sort(mSymbolsPrecededBySpace); 189 mSymbolsFollowedBySpace = new int[] { '.', ',', ';', ':', '!', '?', ')', ']', '}', '&' }; 190 Arrays.sort(mSymbolsFollowedBySpace); 191 mWordConnectors = new int[] { '\'', '-' }; 192 Arrays.sort(mWordConnectors); 193 mSentenceSeparator = Constants.CODE_PERIOD; 194 final String[] suggestPuncsSpec = new String[] { "!", "?", ",", ":", ";" }; 195 mSuggestPuncList = createSuggestPuncList(suggestPuncsSpec); 196 mWordSeparators = "&\t \n()[]{}*&<>+=|.,;:!?/_\""; 197 mHintToSaveText = "Touch again to save"; 198 mCurrentLanguageHasSpaces = true; 199 mInputAttributes = new InputAttributes(null, false /* isFullscreenMode */); 200 mAutoCap = true; 201 mVibrateOn = true; 202 mSoundOn = true; 203 mKeyPreviewPopupOn = true; 204 mSlidingKeyInputPreviewEnabled = true; 205 mShowsVoiceInputKey = true; 206 mIncludesOtherImesInLanguageSwitchList = false; 207 mShowsLanguageSwitchKey = true; 208 mUseContactsDict = true; 209 mUseDoubleSpacePeriod = true; 210 mBlockPotentiallyOffensive = true; 211 mAutoCorrectEnabled = true; 212 mBigramPredictionEnabled = true; 213 mKeyLongpressTimeout = 300; 214 mKeypressVibrationDuration = 5; 215 mKeypressSoundVolume = 1; 216 mKeyPreviewPopupDismissDelay = 70; 217 mAutoCorrectionThreshold = 1; 218 mGestureInputEnabled = true; 219 mGestureTrailEnabled = true; 220 mGestureFloatingPreviewTextEnabled = true; 221 mPhraseGestureEnabled = true; 222 mCorrectionEnabled = mAutoCorrectEnabled && !mInputAttributes.mInputTypeNoAutoCorrect; 223 mSuggestionVisibility = 0; 224 mIsInternal = false; 225 mBoostPersonalizationDictionaryForDebug = false; 226 mUseOnlyPersonalizationDictionaryForDebug = false; 227 } 228 229 @UsedForTesting 230 public static SettingsValues makeDummySettingsValuesForTest(final Locale locale) { 231 return new SettingsValues(locale); 232 } 233 234 public boolean isApplicationSpecifiedCompletionsOn() { 235 return mInputAttributes.mApplicationSpecifiedCompletionOn; 236 } 237 238 public boolean isSuggestionsRequested(final int displayOrientation) { 239 return mInputAttributes.mIsSettingsSuggestionStripOn 240 && (mCorrectionEnabled 241 || isSuggestionStripVisibleInOrientation(displayOrientation)); 242 } 243 244 public boolean isSuggestionStripVisibleInOrientation(final int orientation) { 245 return (mSuggestionVisibility == SUGGESTION_VISIBILITY_SHOW_VALUE) 246 || (mSuggestionVisibility == SUGGESTION_VISIBILITY_SHOW_ONLY_PORTRAIT_VALUE 247 && orientation == Configuration.ORIENTATION_PORTRAIT); 248 } 249 250 public boolean isWordSeparator(final int code) { 251 return mWordSeparators.contains(String.valueOf((char)code)); 252 } 253 254 public boolean isWordConnector(final int code) { 255 return Arrays.binarySearch(mWordConnectors, code) >= 0; 256 } 257 258 public boolean isWordCodePoint(final int code) { 259 return Character.isLetter(code) || isWordConnector(code); 260 } 261 262 public boolean isUsuallyPrecededBySpace(final int code) { 263 return Arrays.binarySearch(mSymbolsPrecededBySpace, code) >= 0; 264 } 265 266 public boolean isUsuallyFollowedBySpace(final int code) { 267 return Arrays.binarySearch(mSymbolsFollowedBySpace, code) >= 0; 268 } 269 270 public boolean shouldInsertSpacesAutomatically() { 271 return mInputAttributes.mShouldInsertSpacesAutomatically; 272 } 273 274 public boolean isVoiceKeyEnabled(final EditorInfo editorInfo) { 275 final boolean shortcutImeEnabled = SubtypeSwitcher.getInstance().isShortcutImeEnabled(); 276 final int inputType = (editorInfo != null) ? editorInfo.inputType : 0; 277 return shortcutImeEnabled && mShowsVoiceInputKey 278 && !InputTypeUtils.isPasswordInputType(inputType); 279 } 280 281 public boolean isLanguageSwitchKeyEnabled() { 282 if (!mShowsLanguageSwitchKey) { 283 return false; 284 } 285 final RichInputMethodManager imm = RichInputMethodManager.getInstance(); 286 if (mIncludesOtherImesInLanguageSwitchList) { 287 return imm.hasMultipleEnabledIMEsOrSubtypes(false /* include aux subtypes */); 288 } else { 289 return imm.hasMultipleEnabledSubtypesInThisIme(false /* include aux subtypes */); 290 } 291 } 292 293 public boolean isSameInputType(final EditorInfo editorInfo) { 294 return mInputAttributes.isSameInputType(editorInfo); 295 } 296 297 // Helper functions to create member values. 298 private static SuggestedWords createSuggestPuncList(final String[] puncs) { 299 final ArrayList<SuggestedWordInfo> puncList = CollectionUtils.newArrayList(); 300 if (puncs != null) { 301 for (final String puncSpec : puncs) { 302 // TODO: Stop using KeySpceParser.getLabel(). 303 puncList.add(new SuggestedWordInfo(KeySpecParser.getLabel(puncSpec), 304 SuggestedWordInfo.MAX_SCORE, SuggestedWordInfo.KIND_HARDCODED, 305 Dictionary.DICTIONARY_HARDCODED, 306 SuggestedWordInfo.NOT_AN_INDEX /* indexOfTouchPointOfSecondWord */, 307 SuggestedWordInfo.NOT_A_CONFIDENCE /* autoCommitFirstWordConfidence */)); 308 } 309 } 310 return new SuggestedWords(puncList, 311 false /* typedWordValid */, 312 false /* hasAutoCorrectionCandidate */, 313 true /* isPunctuationSuggestions */, 314 false /* isObsoleteSuggestions */, 315 false /* isPrediction */); 316 } 317 318 private static final int SUGGESTION_VISIBILITY_SHOW_VALUE = 319 R.string.prefs_suggestion_visibility_show_value; 320 private static final int SUGGESTION_VISIBILITY_SHOW_ONLY_PORTRAIT_VALUE = 321 R.string.prefs_suggestion_visibility_show_only_portrait_value; 322 private static final int SUGGESTION_VISIBILITY_HIDE_VALUE = 323 R.string.prefs_suggestion_visibility_hide_value; 324 private static final int[] SUGGESTION_VISIBILITY_VALUE_ARRAY = new int[] { 325 SUGGESTION_VISIBILITY_SHOW_VALUE, 326 SUGGESTION_VISIBILITY_SHOW_ONLY_PORTRAIT_VALUE, 327 SUGGESTION_VISIBILITY_HIDE_VALUE 328 }; 329 330 private static int createSuggestionVisibility(final Resources res, 331 final String suggestionVisiblityStr) { 332 for (int visibility : SUGGESTION_VISIBILITY_VALUE_ARRAY) { 333 if (suggestionVisiblityStr.equals(res.getString(visibility))) { 334 return visibility; 335 } 336 } 337 throw new RuntimeException("Bug: visibility string is not configured correctly"); 338 } 339 340 private static boolean readBigramPredictionEnabled(final SharedPreferences prefs, 341 final Resources res) { 342 return prefs.getBoolean(Settings.PREF_BIGRAM_PREDICTIONS, res.getBoolean( 343 R.bool.config_default_next_word_prediction)); 344 } 345 346 private static float readAutoCorrectionThreshold(final Resources res, 347 final String currentAutoCorrectionSetting) { 348 final String[] autoCorrectionThresholdValues = res.getStringArray( 349 R.array.auto_correction_threshold_values); 350 // When autoCorrectionThreshold is greater than 1.0, it's like auto correction is off. 351 final float autoCorrectionThreshold; 352 try { 353 final int arrayIndex = Integer.valueOf(currentAutoCorrectionSetting); 354 if (arrayIndex >= 0 && arrayIndex < autoCorrectionThresholdValues.length) { 355 final String val = autoCorrectionThresholdValues[arrayIndex]; 356 if (FLOAT_MAX_VALUE_MARKER_STRING.equals(val)) { 357 autoCorrectionThreshold = Float.MAX_VALUE; 358 } else if (FLOAT_NEGATIVE_INFINITY_MARKER_STRING.equals(val)) { 359 autoCorrectionThreshold = Float.NEGATIVE_INFINITY; 360 } else { 361 autoCorrectionThreshold = Float.parseFloat(val); 362 } 363 } else { 364 autoCorrectionThreshold = Float.MAX_VALUE; 365 } 366 } catch (final NumberFormatException e) { 367 // Whenever the threshold settings are correct, never come here. 368 Log.w(TAG, "Cannot load auto correction threshold setting." 369 + " currentAutoCorrectionSetting: " + currentAutoCorrectionSetting 370 + ", autoCorrectionThresholdValues: " 371 + Arrays.toString(autoCorrectionThresholdValues), e); 372 return Float.MAX_VALUE; 373 } 374 return autoCorrectionThreshold; 375 } 376 377 private static boolean needsToShowVoiceInputKey(SharedPreferences prefs, Resources res) { 378 final String voiceModeMain = res.getString(R.string.voice_mode_main); 379 final String voiceMode = prefs.getString(Settings.PREF_VOICE_MODE_OBSOLETE, voiceModeMain); 380 final boolean showsVoiceInputKey = voiceMode == null || voiceMode.equals(voiceModeMain); 381 if (!showsVoiceInputKey) { 382 // Migrate settings from PREF_VOICE_MODE_OBSOLETE to PREF_VOICE_INPUT_KEY 383 // Set voiceModeMain as a value of obsolete voice mode settings. 384 prefs.edit().putString(Settings.PREF_VOICE_MODE_OBSOLETE, voiceModeMain).apply(); 385 // Disable voice input key. 386 prefs.edit().putBoolean(Settings.PREF_VOICE_INPUT_KEY, false).apply(); 387 } 388 return prefs.getBoolean(Settings.PREF_VOICE_INPUT_KEY, true); 389 } 390} 391