Home | History | Annotate | Download | only in latin
      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