/* * Copyright (C) 2011 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.inputmethod.latin.settings; import android.content.Context; import android.content.SharedPreferences; import android.content.pm.PackageInfo; import android.content.res.Configuration; import android.content.res.Resources; import android.os.Build; import android.util.Log; import android.view.inputmethod.EditorInfo; import com.android.inputmethod.compat.AppWorkaroundsUtils; import com.android.inputmethod.latin.InputAttributes; import com.android.inputmethod.latin.R; import com.android.inputmethod.latin.RichInputMethodManager; import com.android.inputmethod.latin.utils.AsyncResultHolder; import com.android.inputmethod.latin.utils.ResourceUtils; import com.android.inputmethod.latin.utils.TargetPackageInfoGetterTask; import java.util.Arrays; import java.util.Locale; import javax.annotation.Nonnull; import javax.annotation.Nullable; /** * When you call the constructor of this class, you may want to change the current system locale by * using {@link com.android.inputmethod.latin.utils.RunInLocale}. */ // Non-final for testing via mock library. public class SettingsValues { private static final String TAG = SettingsValues.class.getSimpleName(); // "floatMaxValue" and "floatNegativeInfinity" are special marker strings for // Float.NEGATIVE_INFINITE and Float.MAX_VALUE. Currently used for auto-correction settings. private static final String FLOAT_MAX_VALUE_MARKER_STRING = "floatMaxValue"; private static final String FLOAT_NEGATIVE_INFINITY_MARKER_STRING = "floatNegativeInfinity"; private static final int TIMEOUT_TO_GET_TARGET_PACKAGE = 5; // seconds public static final float DEFAULT_SIZE_SCALE = 1.0f; // 100% // From resources: public final SpacingAndPunctuations mSpacingAndPunctuations; public final int mDelayInMillisecondsToUpdateOldSuggestions; public final long mDoubleSpacePeriodTimeout; // From configuration: public final Locale mLocale; public final boolean mHasHardwareKeyboard; public final int mDisplayOrientation; // From preferences, in the same order as xml/prefs.xml: public final boolean mAutoCap; public final boolean mVibrateOn; public final boolean mSoundOn; public final boolean mKeyPreviewPopupOn; public final boolean mShowsVoiceInputKey; public final boolean mIncludesOtherImesInLanguageSwitchList; public final boolean mShowsLanguageSwitchKey; public final boolean mUseContactsDict; public final boolean mUsePersonalizedDicts; public final boolean mUseDoubleSpacePeriod; public final boolean mBlockPotentiallyOffensive; // Use bigrams to predict the next word when there is no input for it yet public final boolean mBigramPredictionEnabled; public final boolean mGestureInputEnabled; public final boolean mGestureTrailEnabled; public final boolean mGestureFloatingPreviewTextEnabled; public final boolean mSlidingKeyInputPreviewEnabled; public final int mKeyLongpressTimeout; public final boolean mEnableEmojiAltPhysicalKey; public final boolean mShowAppIcon; public final boolean mIsShowAppIconSettingInPreferences; public final boolean mCloudSyncEnabled; public final boolean mEnableMetricsLogging; public final boolean mShouldShowLxxSuggestionUi; // Use split layout for keyboard. public final boolean mIsSplitKeyboardEnabled; public final int mScreenMetrics; // From the input box @Nonnull public final InputAttributes mInputAttributes; // Deduced settings public final int mKeypressVibrationDuration; public final float mKeypressSoundVolume; public final int mKeyPreviewPopupDismissDelay; private final boolean mAutoCorrectEnabled; public final float mAutoCorrectionThreshold; public final float mPlausibilityThreshold; public final boolean mAutoCorrectionEnabledPerUserSettings; private final boolean mSuggestionsEnabledPerUserSettings; private final AsyncResultHolder mAppWorkarounds; // Debug settings public final boolean mIsInternal; public final boolean mHasCustomKeyPreviewAnimationParams; public final boolean mHasKeyboardResize; public final float mKeyboardHeightScale; public final int mKeyPreviewShowUpDuration; public final int mKeyPreviewDismissDuration; public final float mKeyPreviewShowUpStartXScale; public final float mKeyPreviewShowUpStartYScale; public final float mKeyPreviewDismissEndXScale; public final float mKeyPreviewDismissEndYScale; @Nullable public final String mAccount; public SettingsValues(final Context context, final SharedPreferences prefs, final Resources res, @Nonnull final InputAttributes inputAttributes) { mLocale = res.getConfiguration().locale; // Get the resources mDelayInMillisecondsToUpdateOldSuggestions = res.getInteger(R.integer.config_delay_in_milliseconds_to_update_old_suggestions); mSpacingAndPunctuations = new SpacingAndPunctuations(res); // Store the input attributes mInputAttributes = inputAttributes; // Get the settings preferences mAutoCap = prefs.getBoolean(Settings.PREF_AUTO_CAP, true); mVibrateOn = Settings.readVibrationEnabled(prefs, res); mSoundOn = Settings.readKeypressSoundEnabled(prefs, res); mKeyPreviewPopupOn = Settings.readKeyPreviewPopupEnabled(prefs, res); mSlidingKeyInputPreviewEnabled = prefs.getBoolean( DebugSettings.PREF_SLIDING_KEY_INPUT_PREVIEW, true); mShowsVoiceInputKey = needsToShowVoiceInputKey(prefs, res) && mInputAttributes.mShouldShowVoiceInputKey && Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN; mIncludesOtherImesInLanguageSwitchList = Settings.ENABLE_SHOW_LANGUAGE_SWITCH_KEY_SETTINGS ? prefs.getBoolean(Settings.PREF_INCLUDE_OTHER_IMES_IN_LANGUAGE_SWITCH_LIST, false) : true /* forcibly */; mShowsLanguageSwitchKey = Settings.ENABLE_SHOW_LANGUAGE_SWITCH_KEY_SETTINGS ? Settings.readShowsLanguageSwitchKey(prefs) : true /* forcibly */; mUseContactsDict = prefs.getBoolean(Settings.PREF_KEY_USE_CONTACTS_DICT, true); mUsePersonalizedDicts = prefs.getBoolean(Settings.PREF_KEY_USE_PERSONALIZED_DICTS, true); mUseDoubleSpacePeriod = prefs.getBoolean(Settings.PREF_KEY_USE_DOUBLE_SPACE_PERIOD, true) && inputAttributes.mIsGeneralTextInput; mBlockPotentiallyOffensive = Settings.readBlockPotentiallyOffensive(prefs, res); mAutoCorrectEnabled = Settings.readAutoCorrectEnabled(prefs, res); final String autoCorrectionThresholdRawValue = mAutoCorrectEnabled ? res.getString(R.string.auto_correction_threshold_mode_index_modest) : res.getString(R.string.auto_correction_threshold_mode_index_off); mBigramPredictionEnabled = readBigramPredictionEnabled(prefs, res); mDoubleSpacePeriodTimeout = res.getInteger(R.integer.config_double_space_period_timeout); mHasHardwareKeyboard = Settings.readHasHardwareKeyboard(res.getConfiguration()); mEnableMetricsLogging = prefs.getBoolean(Settings.PREF_ENABLE_METRICS_LOGGING, true); mIsSplitKeyboardEnabled = prefs.getBoolean(Settings.PREF_ENABLE_SPLIT_KEYBOARD, false); mScreenMetrics = Settings.readScreenMetrics(res); mShouldShowLxxSuggestionUi = Settings.SHOULD_SHOW_LXX_SUGGESTION_UI && prefs.getBoolean(DebugSettings.PREF_SHOULD_SHOW_LXX_SUGGESTION_UI, true); // Compute other readable settings mKeyLongpressTimeout = Settings.readKeyLongpressTimeout(prefs, res); mKeypressVibrationDuration = Settings.readKeypressVibrationDuration(prefs, res); mKeypressSoundVolume = Settings.readKeypressSoundVolume(prefs, res); mKeyPreviewPopupDismissDelay = Settings.readKeyPreviewPopupDismissDelay(prefs, res); mEnableEmojiAltPhysicalKey = prefs.getBoolean( Settings.PREF_ENABLE_EMOJI_ALT_PHYSICAL_KEY, true); mShowAppIcon = Settings.readShowSetupWizardIcon(prefs, context); mIsShowAppIconSettingInPreferences = prefs.contains(Settings.PREF_SHOW_SETUP_WIZARD_ICON); mAutoCorrectionThreshold = readAutoCorrectionThreshold(res, autoCorrectionThresholdRawValue); mPlausibilityThreshold = Settings.readPlausibilityThreshold(res); mGestureInputEnabled = Settings.readGestureInputEnabled(prefs, res); mGestureTrailEnabled = prefs.getBoolean(Settings.PREF_GESTURE_PREVIEW_TRAIL, true); mCloudSyncEnabled = prefs.getBoolean(LocalSettingsConstants.PREF_ENABLE_CLOUD_SYNC, false); mAccount = prefs.getString(LocalSettingsConstants.PREF_ACCOUNT_NAME, null /* default */); mGestureFloatingPreviewTextEnabled = !mInputAttributes.mDisableGestureFloatingPreviewText && prefs.getBoolean(Settings.PREF_GESTURE_FLOATING_PREVIEW_TEXT, true); mAutoCorrectionEnabledPerUserSettings = mAutoCorrectEnabled && !mInputAttributes.mInputTypeNoAutoCorrect; mSuggestionsEnabledPerUserSettings = readSuggestionsEnabled(prefs); mIsInternal = Settings.isInternal(prefs); mHasCustomKeyPreviewAnimationParams = prefs.getBoolean( DebugSettings.PREF_HAS_CUSTOM_KEY_PREVIEW_ANIMATION_PARAMS, false); mHasKeyboardResize = prefs.getBoolean(DebugSettings.PREF_RESIZE_KEYBOARD, false); mKeyboardHeightScale = Settings.readKeyboardHeight(prefs, DEFAULT_SIZE_SCALE); mKeyPreviewShowUpDuration = Settings.readKeyPreviewAnimationDuration( prefs, DebugSettings.PREF_KEY_PREVIEW_SHOW_UP_DURATION, res.getInteger(R.integer.config_key_preview_show_up_duration)); mKeyPreviewDismissDuration = Settings.readKeyPreviewAnimationDuration( prefs, DebugSettings.PREF_KEY_PREVIEW_DISMISS_DURATION, res.getInteger(R.integer.config_key_preview_dismiss_duration)); final float defaultKeyPreviewShowUpStartScale = ResourceUtils.getFloatFromFraction( res, R.fraction.config_key_preview_show_up_start_scale); final float defaultKeyPreviewDismissEndScale = ResourceUtils.getFloatFromFraction( res, R.fraction.config_key_preview_dismiss_end_scale); mKeyPreviewShowUpStartXScale = Settings.readKeyPreviewAnimationScale( prefs, DebugSettings.PREF_KEY_PREVIEW_SHOW_UP_START_X_SCALE, defaultKeyPreviewShowUpStartScale); mKeyPreviewShowUpStartYScale = Settings.readKeyPreviewAnimationScale( prefs, DebugSettings.PREF_KEY_PREVIEW_SHOW_UP_START_Y_SCALE, defaultKeyPreviewShowUpStartScale); mKeyPreviewDismissEndXScale = Settings.readKeyPreviewAnimationScale( prefs, DebugSettings.PREF_KEY_PREVIEW_DISMISS_END_X_SCALE, defaultKeyPreviewDismissEndScale); mKeyPreviewDismissEndYScale = Settings.readKeyPreviewAnimationScale( prefs, DebugSettings.PREF_KEY_PREVIEW_DISMISS_END_Y_SCALE, defaultKeyPreviewDismissEndScale); mDisplayOrientation = res.getConfiguration().orientation; mAppWorkarounds = new AsyncResultHolder<>("AppWorkarounds"); final PackageInfo packageInfo = TargetPackageInfoGetterTask.getCachedPackageInfo( mInputAttributes.mTargetApplicationPackageName); if (null != packageInfo) { mAppWorkarounds.set(new AppWorkaroundsUtils(packageInfo)); } else { new TargetPackageInfoGetterTask(context, mAppWorkarounds) .execute(mInputAttributes.mTargetApplicationPackageName); } } public boolean isMetricsLoggingEnabled() { return mEnableMetricsLogging; } public boolean isApplicationSpecifiedCompletionsOn() { return mInputAttributes.mApplicationSpecifiedCompletionOn; } public boolean needsToLookupSuggestions() { return mInputAttributes.mShouldShowSuggestions && (mAutoCorrectionEnabledPerUserSettings || isSuggestionsEnabledPerUserSettings()); } public boolean isSuggestionsEnabledPerUserSettings() { return mSuggestionsEnabledPerUserSettings; } public boolean isPersonalizationEnabled() { return mUsePersonalizedDicts; } public boolean isWordSeparator(final int code) { return mSpacingAndPunctuations.isWordSeparator(code); } public boolean isWordConnector(final int code) { return mSpacingAndPunctuations.isWordConnector(code); } public boolean isWordCodePoint(final int code) { return Character.isLetter(code) || isWordConnector(code) || Character.COMBINING_SPACING_MARK == Character.getType(code); } public boolean isUsuallyPrecededBySpace(final int code) { return mSpacingAndPunctuations.isUsuallyPrecededBySpace(code); } public boolean isUsuallyFollowedBySpace(final int code) { return mSpacingAndPunctuations.isUsuallyFollowedBySpace(code); } public boolean shouldInsertSpacesAutomatically() { return mInputAttributes.mShouldInsertSpacesAutomatically; } public boolean isLanguageSwitchKeyEnabled() { if (!mShowsLanguageSwitchKey) { return false; } final RichInputMethodManager imm = RichInputMethodManager.getInstance(); if (mIncludesOtherImesInLanguageSwitchList) { return imm.hasMultipleEnabledIMEsOrSubtypes(false /* include aux subtypes */); } return imm.hasMultipleEnabledSubtypesInThisIme(false /* include aux subtypes */); } public boolean isSameInputType(final EditorInfo editorInfo) { return mInputAttributes.isSameInputType(editorInfo); } public boolean hasSameOrientation(final Configuration configuration) { return mDisplayOrientation == configuration.orientation; } public boolean isBeforeJellyBean() { final AppWorkaroundsUtils appWorkaroundUtils = mAppWorkarounds.get(null, TIMEOUT_TO_GET_TARGET_PACKAGE); return null == appWorkaroundUtils ? false : appWorkaroundUtils.isBeforeJellyBean(); } public boolean isBrokenByRecorrection() { final AppWorkaroundsUtils appWorkaroundUtils = mAppWorkarounds.get(null, TIMEOUT_TO_GET_TARGET_PACKAGE); return null == appWorkaroundUtils ? false : appWorkaroundUtils.isBrokenByRecorrection(); } private static final String SUGGESTIONS_VISIBILITY_HIDE_VALUE_OBSOLETE = "2"; private static boolean readSuggestionsEnabled(final SharedPreferences prefs) { if (prefs.contains(Settings.PREF_SHOW_SUGGESTIONS_SETTING_OBSOLETE)) { final boolean alwaysHide = SUGGESTIONS_VISIBILITY_HIDE_VALUE_OBSOLETE.equals( prefs.getString(Settings.PREF_SHOW_SUGGESTIONS_SETTING_OBSOLETE, null)); prefs.edit() .remove(Settings.PREF_SHOW_SUGGESTIONS_SETTING_OBSOLETE) .putBoolean(Settings.PREF_SHOW_SUGGESTIONS, !alwaysHide) .apply(); } return prefs.getBoolean(Settings.PREF_SHOW_SUGGESTIONS, true); } private static boolean readBigramPredictionEnabled(final SharedPreferences prefs, final Resources res) { return prefs.getBoolean(Settings.PREF_BIGRAM_PREDICTIONS, res.getBoolean( R.bool.config_default_next_word_prediction)); } private static float readAutoCorrectionThreshold(final Resources res, final String currentAutoCorrectionSetting) { final String[] autoCorrectionThresholdValues = res.getStringArray( R.array.auto_correction_threshold_values); // When autoCorrectionThreshold is greater than 1.0, it's like auto correction is off. final float autoCorrectionThreshold; try { final int arrayIndex = Integer.parseInt(currentAutoCorrectionSetting); if (arrayIndex >= 0 && arrayIndex < autoCorrectionThresholdValues.length) { final String val = autoCorrectionThresholdValues[arrayIndex]; if (FLOAT_MAX_VALUE_MARKER_STRING.equals(val)) { autoCorrectionThreshold = Float.MAX_VALUE; } else if (FLOAT_NEGATIVE_INFINITY_MARKER_STRING.equals(val)) { autoCorrectionThreshold = Float.NEGATIVE_INFINITY; } else { autoCorrectionThreshold = Float.parseFloat(val); } } else { autoCorrectionThreshold = Float.MAX_VALUE; } } catch (final NumberFormatException e) { // Whenever the threshold settings are correct, never come here. Log.w(TAG, "Cannot load auto correction threshold setting." + " currentAutoCorrectionSetting: " + currentAutoCorrectionSetting + ", autoCorrectionThresholdValues: " + Arrays.toString(autoCorrectionThresholdValues), e); return Float.MAX_VALUE; } return autoCorrectionThreshold; } private static boolean needsToShowVoiceInputKey(final SharedPreferences prefs, final Resources res) { // Migrate preference from {@link Settings#PREF_VOICE_MODE_OBSOLETE} to // {@link Settings#PREF_VOICE_INPUT_KEY}. if (prefs.contains(Settings.PREF_VOICE_MODE_OBSOLETE)) { final String voiceModeMain = res.getString(R.string.voice_mode_main); final String voiceMode = prefs.getString( Settings.PREF_VOICE_MODE_OBSOLETE, voiceModeMain); final boolean shouldShowVoiceInputKey = voiceModeMain.equals(voiceMode); prefs.edit() .putBoolean(Settings.PREF_VOICE_INPUT_KEY, shouldShowVoiceInputKey) // Remove the obsolete preference if exists. .remove(Settings.PREF_VOICE_MODE_OBSOLETE) .apply(); } return prefs.getBoolean(Settings.PREF_VOICE_INPUT_KEY, true); } public String dump() { final StringBuilder sb = new StringBuilder("Current settings :"); sb.append("\n mSpacingAndPunctuations = "); sb.append("" + mSpacingAndPunctuations.dump()); sb.append("\n mDelayInMillisecondsToUpdateOldSuggestions = "); sb.append("" + mDelayInMillisecondsToUpdateOldSuggestions); sb.append("\n mAutoCap = "); sb.append("" + mAutoCap); sb.append("\n mVibrateOn = "); sb.append("" + mVibrateOn); sb.append("\n mSoundOn = "); sb.append("" + mSoundOn); sb.append("\n mKeyPreviewPopupOn = "); sb.append("" + mKeyPreviewPopupOn); sb.append("\n mShowsVoiceInputKey = "); sb.append("" + mShowsVoiceInputKey); sb.append("\n mIncludesOtherImesInLanguageSwitchList = "); sb.append("" + mIncludesOtherImesInLanguageSwitchList); sb.append("\n mShowsLanguageSwitchKey = "); sb.append("" + mShowsLanguageSwitchKey); sb.append("\n mUseContactsDict = "); sb.append("" + mUseContactsDict); sb.append("\n mUsePersonalizedDicts = "); sb.append("" + mUsePersonalizedDicts); sb.append("\n mUseDoubleSpacePeriod = "); sb.append("" + mUseDoubleSpacePeriod); sb.append("\n mBlockPotentiallyOffensive = "); sb.append("" + mBlockPotentiallyOffensive); sb.append("\n mBigramPredictionEnabled = "); sb.append("" + mBigramPredictionEnabled); sb.append("\n mGestureInputEnabled = "); sb.append("" + mGestureInputEnabled); sb.append("\n mGestureTrailEnabled = "); sb.append("" + mGestureTrailEnabled); sb.append("\n mGestureFloatingPreviewTextEnabled = "); sb.append("" + mGestureFloatingPreviewTextEnabled); sb.append("\n mSlidingKeyInputPreviewEnabled = "); sb.append("" + mSlidingKeyInputPreviewEnabled); sb.append("\n mKeyLongpressTimeout = "); sb.append("" + mKeyLongpressTimeout); sb.append("\n mLocale = "); sb.append("" + mLocale); sb.append("\n mInputAttributes = "); sb.append("" + mInputAttributes); sb.append("\n mKeypressVibrationDuration = "); sb.append("" + mKeypressVibrationDuration); sb.append("\n mKeypressSoundVolume = "); sb.append("" + mKeypressSoundVolume); sb.append("\n mKeyPreviewPopupDismissDelay = "); sb.append("" + mKeyPreviewPopupDismissDelay); sb.append("\n mAutoCorrectEnabled = "); sb.append("" + mAutoCorrectEnabled); sb.append("\n mAutoCorrectionThreshold = "); sb.append("" + mAutoCorrectionThreshold); sb.append("\n mAutoCorrectionEnabledPerUserSettings = "); sb.append("" + mAutoCorrectionEnabledPerUserSettings); sb.append("\n mSuggestionsEnabledPerUserSettings = "); sb.append("" + mSuggestionsEnabledPerUserSettings); sb.append("\n mDisplayOrientation = "); sb.append("" + mDisplayOrientation); sb.append("\n mAppWorkarounds = "); final AppWorkaroundsUtils awu = mAppWorkarounds.get(null, 0); sb.append("" + (null == awu ? "null" : awu.toString())); sb.append("\n mIsInternal = "); sb.append("" + mIsInternal); sb.append("\n mKeyPreviewShowUpDuration = "); sb.append("" + mKeyPreviewShowUpDuration); sb.append("\n mKeyPreviewDismissDuration = "); sb.append("" + mKeyPreviewDismissDuration); sb.append("\n mKeyPreviewShowUpStartScaleX = "); sb.append("" + mKeyPreviewShowUpStartXScale); sb.append("\n mKeyPreviewShowUpStartScaleY = "); sb.append("" + mKeyPreviewShowUpStartYScale); sb.append("\n mKeyPreviewDismissEndScaleX = "); sb.append("" + mKeyPreviewDismissEndXScale); sb.append("\n mKeyPreviewDismissEndScaleY = "); sb.append("" + mKeyPreviewDismissEndYScale); return sb.toString(); } }