1/* 2 * Copyright (C) 2013 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.Context; 20import android.content.SharedPreferences; 21import android.content.pm.ApplicationInfo; 22import android.content.res.Configuration; 23import android.content.res.Resources; 24import android.os.Build; 25import android.preference.PreferenceManager; 26import android.util.Log; 27 28import com.android.inputmethod.compat.BuildCompatUtils; 29import com.android.inputmethod.latin.AudioAndHapticFeedbackManager; 30import com.android.inputmethod.latin.InputAttributes; 31import com.android.inputmethod.latin.R; 32import com.android.inputmethod.latin.common.StringUtils; 33import com.android.inputmethod.latin.utils.AdditionalSubtypeUtils; 34import com.android.inputmethod.latin.utils.ResourceUtils; 35import com.android.inputmethod.latin.utils.RunInLocale; 36import com.android.inputmethod.latin.utils.StatsUtils; 37 38import java.util.Collections; 39import java.util.Locale; 40import java.util.Set; 41import java.util.concurrent.locks.ReentrantLock; 42 43import javax.annotation.Nonnull; 44 45public final class Settings implements SharedPreferences.OnSharedPreferenceChangeListener { 46 private static final String TAG = Settings.class.getSimpleName(); 47 // Settings screens 48 public static final String SCREEN_ACCOUNTS = "screen_accounts"; 49 public static final String SCREEN_THEME = "screen_theme"; 50 public static final String SCREEN_DEBUG = "screen_debug"; 51 // In the same order as xml/prefs.xml 52 public static final String PREF_AUTO_CAP = "auto_cap"; 53 public static final String PREF_VIBRATE_ON = "vibrate_on"; 54 public static final String PREF_SOUND_ON = "sound_on"; 55 public static final String PREF_POPUP_ON = "popup_on"; 56 // PREF_VOICE_MODE_OBSOLETE is obsolete. Use PREF_VOICE_INPUT_KEY instead. 57 public static final String PREF_VOICE_MODE_OBSOLETE = "voice_mode"; 58 public static final String PREF_VOICE_INPUT_KEY = "pref_voice_input_key"; 59 public static final String PREF_EDIT_PERSONAL_DICTIONARY = "edit_personal_dictionary"; 60 public static final String PREF_CONFIGURE_DICTIONARIES_KEY = "configure_dictionaries_key"; 61 // PREF_AUTO_CORRECTION_THRESHOLD_OBSOLETE is obsolete. Use PREF_AUTO_CORRECTION instead. 62 public static final String PREF_AUTO_CORRECTION_THRESHOLD_OBSOLETE = 63 "auto_correction_threshold"; 64 public static final String PREF_AUTO_CORRECTION = "pref_key_auto_correction"; 65 // PREF_SHOW_SUGGESTIONS_SETTING_OBSOLETE is obsolete. Use PREF_SHOW_SUGGESTIONS instead. 66 public static final String PREF_SHOW_SUGGESTIONS_SETTING_OBSOLETE = "show_suggestions_setting"; 67 public static final String PREF_SHOW_SUGGESTIONS = "show_suggestions"; 68 public static final String PREF_KEY_USE_CONTACTS_DICT = "pref_key_use_contacts_dict"; 69 public static final String PREF_KEY_USE_PERSONALIZED_DICTS = "pref_key_use_personalized_dicts"; 70 public static final String PREF_KEY_USE_DOUBLE_SPACE_PERIOD = 71 "pref_key_use_double_space_period"; 72 public static final String PREF_BLOCK_POTENTIALLY_OFFENSIVE = 73 "pref_key_block_potentially_offensive"; 74 public static final boolean ENABLE_SHOW_LANGUAGE_SWITCH_KEY_SETTINGS = 75 BuildCompatUtils.EFFECTIVE_SDK_INT <= Build.VERSION_CODES.KITKAT; 76 public static final boolean SHOULD_SHOW_LXX_SUGGESTION_UI = 77 BuildCompatUtils.EFFECTIVE_SDK_INT >= Build.VERSION_CODES.LOLLIPOP; 78 public static final String PREF_SHOW_LANGUAGE_SWITCH_KEY = 79 "pref_show_language_switch_key"; 80 public static final String PREF_INCLUDE_OTHER_IMES_IN_LANGUAGE_SWITCH_LIST = 81 "pref_include_other_imes_in_language_switch_list"; 82 public static final String PREF_CUSTOM_INPUT_STYLES = "custom_input_styles"; 83 public static final String PREF_ENABLE_SPLIT_KEYBOARD = "pref_split_keyboard"; 84 // TODO: consolidate key preview dismiss delay with the key preview animation parameters. 85 public static final String PREF_KEY_PREVIEW_POPUP_DISMISS_DELAY = 86 "pref_key_preview_popup_dismiss_delay"; 87 public static final String PREF_BIGRAM_PREDICTIONS = "next_word_prediction"; 88 public static final String PREF_GESTURE_INPUT = "gesture_input"; 89 public static final String PREF_VIBRATION_DURATION_SETTINGS = 90 "pref_vibration_duration_settings"; 91 public static final String PREF_KEYPRESS_SOUND_VOLUME = "pref_keypress_sound_volume"; 92 public static final String PREF_KEY_LONGPRESS_TIMEOUT = "pref_key_longpress_timeout"; 93 public static final String PREF_ENABLE_EMOJI_ALT_PHYSICAL_KEY = 94 "pref_enable_emoji_alt_physical_key"; 95 public static final String PREF_GESTURE_PREVIEW_TRAIL = "pref_gesture_preview_trail"; 96 public static final String PREF_GESTURE_FLOATING_PREVIEW_TEXT = 97 "pref_gesture_floating_preview_text"; 98 public static final String PREF_SHOW_SETUP_WIZARD_ICON = "pref_show_setup_wizard_icon"; 99 100 public static final String PREF_KEY_IS_INTERNAL = "pref_key_is_internal"; 101 102 public static final String PREF_ENABLE_METRICS_LOGGING = "pref_enable_metrics_logging"; 103 // This preference key is deprecated. Use {@link #PREF_SHOW_LANGUAGE_SWITCH_KEY} instead. 104 // This is being used only for the backward compatibility. 105 private static final String PREF_SUPPRESS_LANGUAGE_SWITCH_KEY = 106 "pref_suppress_language_switch_key"; 107 108 private static final String PREF_LAST_USED_PERSONALIZATION_TOKEN = 109 "pref_last_used_personalization_token"; 110 private static final String PREF_LAST_PERSONALIZATION_DICT_WIPED_TIME = 111 "pref_last_used_personalization_dict_wiped_time"; 112 private static final String PREF_CORPUS_HANDLES_FOR_PERSONALIZATION = 113 "pref_corpus_handles_for_personalization"; 114 115 // Emoji 116 public static final String PREF_EMOJI_RECENT_KEYS = "emoji_recent_keys"; 117 public static final String PREF_EMOJI_CATEGORY_LAST_TYPED_ID = "emoji_category_last_typed_id"; 118 public static final String PREF_LAST_SHOWN_EMOJI_CATEGORY_ID = "last_shown_emoji_category_id"; 119 120 private static final float UNDEFINED_PREFERENCE_VALUE_FLOAT = -1.0f; 121 private static final int UNDEFINED_PREFERENCE_VALUE_INT = -1; 122 123 private Context mContext; 124 private Resources mRes; 125 private SharedPreferences mPrefs; 126 private SettingsValues mSettingsValues; 127 private final ReentrantLock mSettingsValuesLock = new ReentrantLock(); 128 129 private static final Settings sInstance = new Settings(); 130 131 public static Settings getInstance() { 132 return sInstance; 133 } 134 135 public static void init(final Context context) { 136 sInstance.onCreate(context); 137 } 138 139 private Settings() { 140 // Intentional empty constructor for singleton. 141 } 142 143 private void onCreate(final Context context) { 144 mContext = context; 145 mRes = context.getResources(); 146 mPrefs = PreferenceManager.getDefaultSharedPreferences(context); 147 mPrefs.registerOnSharedPreferenceChangeListener(this); 148 upgradeAutocorrectionSettings(mPrefs, mRes); 149 } 150 151 public void onDestroy() { 152 mPrefs.unregisterOnSharedPreferenceChangeListener(this); 153 } 154 155 @Override 156 public void onSharedPreferenceChanged(final SharedPreferences prefs, final String key) { 157 mSettingsValuesLock.lock(); 158 try { 159 if (mSettingsValues == null) { 160 // TODO: Introduce a static function to register this class and ensure that 161 // loadSettings must be called before "onSharedPreferenceChanged" is called. 162 Log.w(TAG, "onSharedPreferenceChanged called before loadSettings."); 163 return; 164 } 165 loadSettings(mContext, mSettingsValues.mLocale, mSettingsValues.mInputAttributes); 166 StatsUtils.onLoadSettings(mSettingsValues); 167 } finally { 168 mSettingsValuesLock.unlock(); 169 } 170 } 171 172 public void loadSettings(final Context context, final Locale locale, 173 @Nonnull final InputAttributes inputAttributes) { 174 mSettingsValuesLock.lock(); 175 mContext = context; 176 try { 177 final SharedPreferences prefs = mPrefs; 178 final RunInLocale<SettingsValues> job = new RunInLocale<SettingsValues>() { 179 @Override 180 protected SettingsValues job(final Resources res) { 181 return new SettingsValues(context, prefs, res, inputAttributes); 182 } 183 }; 184 mSettingsValues = job.runInLocale(mRes, locale); 185 } finally { 186 mSettingsValuesLock.unlock(); 187 } 188 } 189 190 // TODO: Remove this method and add proxy method to SettingsValues. 191 public SettingsValues getCurrent() { 192 return mSettingsValues; 193 } 194 195 public boolean isInternal() { 196 return mSettingsValues.mIsInternal; 197 } 198 199 public static int readScreenMetrics(final Resources res) { 200 return res.getInteger(R.integer.config_screen_metrics); 201 } 202 203 // Accessed from the settings interface, hence public 204 public static boolean readKeypressSoundEnabled(final SharedPreferences prefs, 205 final Resources res) { 206 return prefs.getBoolean(PREF_SOUND_ON, 207 res.getBoolean(R.bool.config_default_sound_enabled)); 208 } 209 210 public static boolean readVibrationEnabled(final SharedPreferences prefs, 211 final Resources res) { 212 final boolean hasVibrator = AudioAndHapticFeedbackManager.getInstance().hasVibrator(); 213 return hasVibrator && prefs.getBoolean(PREF_VIBRATE_ON, 214 res.getBoolean(R.bool.config_default_vibration_enabled)); 215 } 216 217 public static boolean readAutoCorrectEnabled(final SharedPreferences prefs, 218 final Resources res) { 219 return prefs.getBoolean(PREF_AUTO_CORRECTION, true); 220 } 221 222 public static float readPlausibilityThreshold(final Resources res) { 223 return Float.parseFloat(res.getString(R.string.plausibility_threshold)); 224 } 225 226 public static boolean readBlockPotentiallyOffensive(final SharedPreferences prefs, 227 final Resources res) { 228 return prefs.getBoolean(PREF_BLOCK_POTENTIALLY_OFFENSIVE, 229 res.getBoolean(R.bool.config_block_potentially_offensive)); 230 } 231 232 public static boolean readFromBuildConfigIfGestureInputEnabled(final Resources res) { 233 return res.getBoolean(R.bool.config_gesture_input_enabled_by_build_config); 234 } 235 236 public static boolean readGestureInputEnabled(final SharedPreferences prefs, 237 final Resources res) { 238 return readFromBuildConfigIfGestureInputEnabled(res) 239 && prefs.getBoolean(PREF_GESTURE_INPUT, true); 240 } 241 242 public static boolean readFromBuildConfigIfToShowKeyPreviewPopupOption(final Resources res) { 243 return res.getBoolean(R.bool.config_enable_show_key_preview_popup_option); 244 } 245 246 public static boolean readKeyPreviewPopupEnabled(final SharedPreferences prefs, 247 final Resources res) { 248 final boolean defaultKeyPreviewPopup = res.getBoolean( 249 R.bool.config_default_key_preview_popup); 250 if (!readFromBuildConfigIfToShowKeyPreviewPopupOption(res)) { 251 return defaultKeyPreviewPopup; 252 } 253 return prefs.getBoolean(PREF_POPUP_ON, defaultKeyPreviewPopup); 254 } 255 256 public static int readKeyPreviewPopupDismissDelay(final SharedPreferences prefs, 257 final Resources res) { 258 return Integer.parseInt(prefs.getString(PREF_KEY_PREVIEW_POPUP_DISMISS_DELAY, 259 Integer.toString(res.getInteger( 260 R.integer.config_key_preview_linger_timeout)))); 261 } 262 263 public static boolean readShowsLanguageSwitchKey(final SharedPreferences prefs) { 264 if (prefs.contains(PREF_SUPPRESS_LANGUAGE_SWITCH_KEY)) { 265 final boolean suppressLanguageSwitchKey = prefs.getBoolean( 266 PREF_SUPPRESS_LANGUAGE_SWITCH_KEY, false); 267 final SharedPreferences.Editor editor = prefs.edit(); 268 editor.remove(PREF_SUPPRESS_LANGUAGE_SWITCH_KEY); 269 editor.putBoolean(PREF_SHOW_LANGUAGE_SWITCH_KEY, !suppressLanguageSwitchKey); 270 editor.apply(); 271 } 272 return prefs.getBoolean(PREF_SHOW_LANGUAGE_SWITCH_KEY, true); 273 } 274 275 public static String readPrefAdditionalSubtypes(final SharedPreferences prefs, 276 final Resources res) { 277 final String predefinedPrefSubtypes = AdditionalSubtypeUtils.createPrefSubtypes( 278 res.getStringArray(R.array.predefined_subtypes)); 279 return prefs.getString(PREF_CUSTOM_INPUT_STYLES, predefinedPrefSubtypes); 280 } 281 282 public static void writePrefAdditionalSubtypes(final SharedPreferences prefs, 283 final String prefSubtypes) { 284 prefs.edit().putString(PREF_CUSTOM_INPUT_STYLES, prefSubtypes).apply(); 285 } 286 287 public static float readKeypressSoundVolume(final SharedPreferences prefs, 288 final Resources res) { 289 final float volume = prefs.getFloat( 290 PREF_KEYPRESS_SOUND_VOLUME, UNDEFINED_PREFERENCE_VALUE_FLOAT); 291 return (volume != UNDEFINED_PREFERENCE_VALUE_FLOAT) ? volume 292 : readDefaultKeypressSoundVolume(res); 293 } 294 295 // Default keypress sound volume for unknown devices. 296 // The negative value means system default. 297 private static final String DEFAULT_KEYPRESS_SOUND_VOLUME = Float.toString(-1.0f); 298 299 public static float readDefaultKeypressSoundVolume(final Resources res) { 300 return Float.parseFloat(ResourceUtils.getDeviceOverrideValue(res, 301 R.array.keypress_volumes, DEFAULT_KEYPRESS_SOUND_VOLUME)); 302 } 303 304 public static int readKeyLongpressTimeout(final SharedPreferences prefs, 305 final Resources res) { 306 final int milliseconds = prefs.getInt( 307 PREF_KEY_LONGPRESS_TIMEOUT, UNDEFINED_PREFERENCE_VALUE_INT); 308 return (milliseconds != UNDEFINED_PREFERENCE_VALUE_INT) ? milliseconds 309 : readDefaultKeyLongpressTimeout(res); 310 } 311 312 public static int readDefaultKeyLongpressTimeout(final Resources res) { 313 return res.getInteger(R.integer.config_default_longpress_key_timeout); 314 } 315 316 public static int readKeypressVibrationDuration(final SharedPreferences prefs, 317 final Resources res) { 318 final int milliseconds = prefs.getInt( 319 PREF_VIBRATION_DURATION_SETTINGS, UNDEFINED_PREFERENCE_VALUE_INT); 320 return (milliseconds != UNDEFINED_PREFERENCE_VALUE_INT) ? milliseconds 321 : readDefaultKeypressVibrationDuration(res); 322 } 323 324 // Default keypress vibration duration for unknown devices. 325 // The negative value means system default. 326 private static final String DEFAULT_KEYPRESS_VIBRATION_DURATION = Integer.toString(-1); 327 328 public static int readDefaultKeypressVibrationDuration(final Resources res) { 329 return Integer.parseInt(ResourceUtils.getDeviceOverrideValue(res, 330 R.array.keypress_vibration_durations, DEFAULT_KEYPRESS_VIBRATION_DURATION)); 331 } 332 333 public static float readKeyPreviewAnimationScale(final SharedPreferences prefs, 334 final String prefKey, final float defaultValue) { 335 final float fraction = prefs.getFloat(prefKey, UNDEFINED_PREFERENCE_VALUE_FLOAT); 336 return (fraction != UNDEFINED_PREFERENCE_VALUE_FLOAT) ? fraction : defaultValue; 337 } 338 339 public static int readKeyPreviewAnimationDuration(final SharedPreferences prefs, 340 final String prefKey, final int defaultValue) { 341 final int milliseconds = prefs.getInt(prefKey, UNDEFINED_PREFERENCE_VALUE_INT); 342 return (milliseconds != UNDEFINED_PREFERENCE_VALUE_INT) ? milliseconds : defaultValue; 343 } 344 345 public static float readKeyboardHeight(final SharedPreferences prefs, 346 final float defaultValue) { 347 final float percentage = prefs.getFloat( 348 DebugSettings.PREF_KEYBOARD_HEIGHT_SCALE, UNDEFINED_PREFERENCE_VALUE_FLOAT); 349 return (percentage != UNDEFINED_PREFERENCE_VALUE_FLOAT) ? percentage : defaultValue; 350 } 351 352 public static boolean readUseFullscreenMode(final Resources res) { 353 return res.getBoolean(R.bool.config_use_fullscreen_mode); 354 } 355 356 public static boolean readShowSetupWizardIcon(final SharedPreferences prefs, 357 final Context context) { 358 if (!prefs.contains(PREF_SHOW_SETUP_WIZARD_ICON)) { 359 final ApplicationInfo appInfo = context.getApplicationInfo(); 360 final boolean isApplicationInSystemImage = 361 (appInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0; 362 // Default value 363 return !isApplicationInSystemImage; 364 } 365 return prefs.getBoolean(PREF_SHOW_SETUP_WIZARD_ICON, false); 366 } 367 368 public static boolean readHasHardwareKeyboard(final Configuration conf) { 369 // The standard way of finding out whether we have a hardware keyboard. This code is taken 370 // from InputMethodService#onEvaluateInputShown, which canonically determines this. 371 // In a nutshell, we have a keyboard if the configuration says the type of hardware keyboard 372 // is NOKEYS and if it's not hidden (e.g. folded inside the device). 373 return conf.keyboard != Configuration.KEYBOARD_NOKEYS 374 && conf.hardKeyboardHidden != Configuration.HARDKEYBOARDHIDDEN_YES; 375 } 376 377 public static boolean isInternal(final SharedPreferences prefs) { 378 return prefs.getBoolean(PREF_KEY_IS_INTERNAL, false); 379 } 380 381 public void writeLastUsedPersonalizationToken(byte[] token) { 382 if (token == null) { 383 mPrefs.edit().remove(PREF_LAST_USED_PERSONALIZATION_TOKEN).apply(); 384 } else { 385 final String tokenStr = StringUtils.byteArrayToHexString(token); 386 mPrefs.edit().putString(PREF_LAST_USED_PERSONALIZATION_TOKEN, tokenStr).apply(); 387 } 388 } 389 390 public byte[] readLastUsedPersonalizationToken() { 391 final String tokenStr = mPrefs.getString(PREF_LAST_USED_PERSONALIZATION_TOKEN, null); 392 return StringUtils.hexStringToByteArray(tokenStr); 393 } 394 395 public void writeLastPersonalizationDictWipedTime(final long timestamp) { 396 mPrefs.edit().putLong(PREF_LAST_PERSONALIZATION_DICT_WIPED_TIME, timestamp).apply(); 397 } 398 399 public long readLastPersonalizationDictGeneratedTime() { 400 return mPrefs.getLong(PREF_LAST_PERSONALIZATION_DICT_WIPED_TIME, 0); 401 } 402 403 public void writeCorpusHandlesForPersonalization(final Set<String> corpusHandles) { 404 mPrefs.edit().putStringSet(PREF_CORPUS_HANDLES_FOR_PERSONALIZATION, corpusHandles).apply(); 405 } 406 407 public Set<String> readCorpusHandlesForPersonalization() { 408 final Set<String> emptySet = Collections.emptySet(); 409 return mPrefs.getStringSet(PREF_CORPUS_HANDLES_FOR_PERSONALIZATION, emptySet); 410 } 411 412 public static void writeEmojiRecentKeys(final SharedPreferences prefs, String str) { 413 prefs.edit().putString(PREF_EMOJI_RECENT_KEYS, str).apply(); 414 } 415 416 public static String readEmojiRecentKeys(final SharedPreferences prefs) { 417 return prefs.getString(PREF_EMOJI_RECENT_KEYS, ""); 418 } 419 420 public static void writeLastTypedEmojiCategoryPageId( 421 final SharedPreferences prefs, final int categoryId, final int categoryPageId) { 422 final String key = PREF_EMOJI_CATEGORY_LAST_TYPED_ID + categoryId; 423 prefs.edit().putInt(key, categoryPageId).apply(); 424 } 425 426 public static int readLastTypedEmojiCategoryPageId( 427 final SharedPreferences prefs, final int categoryId) { 428 final String key = PREF_EMOJI_CATEGORY_LAST_TYPED_ID + categoryId; 429 return prefs.getInt(key, 0); 430 } 431 432 public static void writeLastShownEmojiCategoryId( 433 final SharedPreferences prefs, final int categoryId) { 434 prefs.edit().putInt(PREF_LAST_SHOWN_EMOJI_CATEGORY_ID, categoryId).apply(); 435 } 436 437 public static int readLastShownEmojiCategoryId( 438 final SharedPreferences prefs, final int defValue) { 439 return prefs.getInt(PREF_LAST_SHOWN_EMOJI_CATEGORY_ID, defValue); 440 } 441 442 private void upgradeAutocorrectionSettings(final SharedPreferences prefs, final Resources res) { 443 final String thresholdSetting = 444 prefs.getString(PREF_AUTO_CORRECTION_THRESHOLD_OBSOLETE, null); 445 if (thresholdSetting != null) { 446 SharedPreferences.Editor editor = prefs.edit(); 447 editor.remove(PREF_AUTO_CORRECTION_THRESHOLD_OBSOLETE); 448 final String autoCorrectionOff = 449 res.getString(R.string.auto_correction_threshold_mode_index_off); 450 if (thresholdSetting.equals(autoCorrectionOff)) { 451 editor.putBoolean(PREF_AUTO_CORRECTION, false); 452 } else { 453 editor.putBoolean(PREF_AUTO_CORRECTION, true); 454 } 455 editor.commit(); 456 } 457 } 458} 459