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