1 /* 2 * Copyright (C) 2008 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.app.Activity; 20 import android.app.backup.BackupManager; 21 import android.content.Context; 22 import android.content.Intent; 23 import android.content.SharedPreferences; 24 import android.content.pm.PackageManager; 25 import android.content.pm.ResolveInfo; 26 import android.content.res.Resources; 27 import android.media.AudioManager; 28 import android.os.Build; 29 import android.os.Bundle; 30 import android.preference.CheckBoxPreference; 31 import android.preference.ListPreference; 32 import android.preference.Preference; 33 import android.preference.Preference.OnPreferenceClickListener; 34 import android.preference.PreferenceGroup; 35 import android.preference.PreferenceScreen; 36 import android.util.Log; 37 import android.view.inputmethod.InputMethodSubtype; 38 39 import com.android.inputmethod.dictionarypack.DictionarySettingsActivity; 40 import com.android.inputmethod.latin.AudioAndHapticFeedbackManager; 41 import com.android.inputmethod.latin.R; 42 import com.android.inputmethod.latin.SubtypeSwitcher; 43 import com.android.inputmethod.latin.define.ProductionFlag; 44 import com.android.inputmethod.latin.setup.LauncherIconVisibilityManager; 45 import com.android.inputmethod.latin.userdictionary.UserDictionaryList; 46 import com.android.inputmethod.latin.userdictionary.UserDictionarySettings; 47 import com.android.inputmethod.latin.utils.AdditionalSubtypeUtils; 48 import com.android.inputmethod.latin.utils.ApplicationUtils; 49 import com.android.inputmethod.latin.utils.FeedbackUtils; 50 import com.android.inputmethod.latin.utils.SubtypeLocaleUtils; 51 import com.android.inputmethod.research.ResearchLogger; 52 import com.android.inputmethodcommon.InputMethodSettingsFragment; 53 54 import java.util.TreeSet; 55 56 public final class SettingsFragment extends InputMethodSettingsFragment 57 implements SharedPreferences.OnSharedPreferenceChangeListener { 58 private static final String TAG = SettingsFragment.class.getSimpleName(); 59 private static final boolean DBG_USE_INTERNAL_PERSONAL_DICTIONARY_SETTINGS = false; 60 private static final boolean USE_INTERNAL_PERSONAL_DICTIONARY_SETTIGS = 61 DBG_USE_INTERNAL_PERSONAL_DICTIONARY_SETTINGS 62 || Build.VERSION.SDK_INT <= 18 /* Build.VERSION.JELLY_BEAN_MR2 */; 63 64 private CheckBoxPreference mVoiceInputKeyPreference; 65 private ListPreference mShowCorrectionSuggestionsPreference; 66 private ListPreference mAutoCorrectionThresholdPreference; 67 private ListPreference mKeyPreviewPopupDismissDelay; 68 // Use bigrams to predict the next word when there is no input for it yet 69 private CheckBoxPreference mBigramPrediction; 70 71 private void setPreferenceEnabled(final String preferenceKey, final boolean enabled) { 72 final Preference preference = findPreference(preferenceKey); 73 if (preference != null) { 74 preference.setEnabled(enabled); 75 } 76 } 77 78 private static void removePreference(final String preferenceKey, final PreferenceGroup parent) { 79 if (parent == null) { 80 return; 81 } 82 final Preference preference = parent.findPreference(preferenceKey); 83 if (preference != null) { 84 parent.removePreference(preference); 85 } 86 } 87 88 @Override 89 public void onCreate(final Bundle icicle) { 90 super.onCreate(icicle); 91 setInputMethodSettingsCategoryTitle(R.string.language_selection_title); 92 setSubtypeEnablerTitle(R.string.select_language); 93 addPreferencesFromResource(R.xml.prefs); 94 final PreferenceScreen preferenceScreen = getPreferenceScreen(); 95 if (preferenceScreen != null) { 96 preferenceScreen.setTitle( 97 ApplicationUtils.getAcitivityTitleResId(getActivity(), SettingsActivity.class)); 98 } 99 100 final Resources res = getResources(); 101 final Context context = getActivity(); 102 103 // When we are called from the Settings application but we are not already running, some 104 // singleton and utility classes may not have been initialized. We have to call 105 // initialization method of these classes here. See {@link LatinIME#onCreate()}. 106 SubtypeSwitcher.init(context); 107 SubtypeLocaleUtils.init(context); 108 AudioAndHapticFeedbackManager.init(context); 109 110 mVoiceInputKeyPreference = 111 (CheckBoxPreference) findPreference(Settings.PREF_VOICE_INPUT_KEY); 112 mShowCorrectionSuggestionsPreference = 113 (ListPreference) findPreference(Settings.PREF_SHOW_SUGGESTIONS_SETTING); 114 final SharedPreferences prefs = getPreferenceManager().getSharedPreferences(); 115 prefs.registerOnSharedPreferenceChangeListener(this); 116 117 mAutoCorrectionThresholdPreference = 118 (ListPreference) findPreference(Settings.PREF_AUTO_CORRECTION_THRESHOLD); 119 mBigramPrediction = (CheckBoxPreference) findPreference(Settings.PREF_BIGRAM_PREDICTIONS); 120 ensureConsistencyOfAutoCorrectionSettings(); 121 122 final PreferenceGroup generalSettings = 123 (PreferenceGroup) findPreference(Settings.PREF_GENERAL_SETTINGS); 124 final PreferenceGroup miscSettings = 125 (PreferenceGroup) findPreference(Settings.PREF_MISC_SETTINGS); 126 127 final Preference debugSettings = findPreference(Settings.PREF_DEBUG_SETTINGS); 128 if (debugSettings != null) { 129 if (Settings.isInternal(prefs)) { 130 final Intent debugSettingsIntent = new Intent(Intent.ACTION_MAIN); 131 debugSettingsIntent.setClassName( 132 context.getPackageName(), DebugSettingsActivity.class.getName()); 133 debugSettings.setIntent(debugSettingsIntent); 134 } else { 135 miscSettings.removePreference(debugSettings); 136 } 137 } 138 139 final Preference feedbackSettings = findPreference(Settings.PREF_SEND_FEEDBACK); 140 final Preference aboutSettings = findPreference(Settings.PREF_ABOUT_KEYBOARD); 141 if (feedbackSettings != null) { 142 if (FeedbackUtils.isFeedbackFormSupported()) { 143 feedbackSettings.setOnPreferenceClickListener(new OnPreferenceClickListener() { 144 @Override 145 public boolean onPreferenceClick(final Preference pref) { 146 if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) { 147 // Use development-only feedback mechanism 148 ResearchLogger.getInstance().presentFeedbackDialogFromSettings(); 149 } else { 150 FeedbackUtils.showFeedbackForm(getActivity()); 151 } 152 return true; 153 } 154 }); 155 aboutSettings.setTitle(FeedbackUtils.getAboutKeyboardTitleResId()); 156 aboutSettings.setIntent(FeedbackUtils.getAboutKeyboardIntent(getActivity())); 157 } else { 158 miscSettings.removePreference(feedbackSettings); 159 miscSettings.removePreference(aboutSettings); 160 } 161 } 162 if (ProductionFlag.USES_DEVELOPMENT_ONLY_DIAGNOSTICS) { 163 // The about screen contains items that may be confusing in development-only versions. 164 miscSettings.removePreference(aboutSettings); 165 } 166 167 final boolean showVoiceKeyOption = res.getBoolean( 168 R.bool.config_enable_show_voice_key_option); 169 if (!showVoiceKeyOption) { 170 generalSettings.removePreference(mVoiceInputKeyPreference); 171 } 172 173 final PreferenceGroup advancedSettings = 174 (PreferenceGroup) findPreference(Settings.PREF_ADVANCED_SETTINGS); 175 if (!AudioAndHapticFeedbackManager.getInstance().hasVibrator()) { 176 removePreference(Settings.PREF_VIBRATE_ON, generalSettings); 177 removePreference(Settings.PREF_VIBRATION_DURATION_SETTINGS, advancedSettings); 178 } 179 180 mKeyPreviewPopupDismissDelay = 181 (ListPreference) findPreference(Settings.PREF_KEY_PREVIEW_POPUP_DISMISS_DELAY); 182 if (!Settings.readFromBuildConfigIfToShowKeyPreviewPopupSettingsOption(res)) { 183 removePreference(Settings.PREF_POPUP_ON, generalSettings); 184 removePreference(Settings.PREF_KEY_PREVIEW_POPUP_DISMISS_DELAY, advancedSettings); 185 } else { 186 final String popupDismissDelayDefaultValue = Integer.toString(res.getInteger( 187 R.integer.config_key_preview_linger_timeout)); 188 mKeyPreviewPopupDismissDelay.setEntries(new String[] { 189 res.getString(R.string.key_preview_popup_dismiss_no_delay), 190 res.getString(R.string.key_preview_popup_dismiss_default_delay), 191 }); 192 mKeyPreviewPopupDismissDelay.setEntryValues(new String[] { 193 "0", 194 popupDismissDelayDefaultValue 195 }); 196 if (null == mKeyPreviewPopupDismissDelay.getValue()) { 197 mKeyPreviewPopupDismissDelay.setValue(popupDismissDelayDefaultValue); 198 } 199 mKeyPreviewPopupDismissDelay.setEnabled( 200 Settings.readKeyPreviewPopupEnabled(prefs, res)); 201 } 202 203 if (!res.getBoolean(R.bool.config_setup_wizard_available)) { 204 removePreference(Settings.PREF_SHOW_SETUP_WIZARD_ICON, advancedSettings); 205 } 206 207 setPreferenceEnabled(Settings.PREF_INCLUDE_OTHER_IMES_IN_LANGUAGE_SWITCH_LIST, 208 Settings.readShowsLanguageSwitchKey(prefs)); 209 210 final PreferenceGroup textCorrectionGroup = 211 (PreferenceGroup) findPreference(Settings.PREF_CORRECTION_SETTINGS); 212 final PreferenceScreen dictionaryLink = 213 (PreferenceScreen) findPreference(Settings.PREF_CONFIGURE_DICTIONARIES_KEY); 214 final Intent intent = dictionaryLink.getIntent(); 215 intent.setClassName(context.getPackageName(), DictionarySettingsActivity.class.getName()); 216 final int number = context.getPackageManager().queryIntentActivities(intent, 0).size(); 217 if (0 >= number) { 218 textCorrectionGroup.removePreference(dictionaryLink); 219 } 220 221 final Preference editPersonalDictionary = 222 findPreference(Settings.PREF_EDIT_PERSONAL_DICTIONARY); 223 final Intent editPersonalDictionaryIntent = editPersonalDictionary.getIntent(); 224 final ResolveInfo ri = USE_INTERNAL_PERSONAL_DICTIONARY_SETTIGS ? null 225 : context.getPackageManager().resolveActivity( 226 editPersonalDictionaryIntent, PackageManager.MATCH_DEFAULT_ONLY); 227 if (ri == null) { 228 overwriteUserDictionaryPreference(editPersonalDictionary); 229 } 230 231 if (!Settings.readFromBuildConfigIfGestureInputEnabled(res)) { 232 removePreference(Settings.PREF_GESTURE_SETTINGS, getPreferenceScreen()); 233 } 234 235 AdditionalFeaturesSettingUtils.addAdditionalFeaturesPreferences(context, this); 236 237 setupKeyLongpressTimeoutSettings(prefs, res); 238 setupKeypressVibrationDurationSettings(prefs, res); 239 setupKeypressSoundVolumeSettings(prefs, res); 240 refreshEnablingsOfKeypressSoundAndVibrationSettings(prefs, res); 241 } 242 243 @Override 244 public void onResume() { 245 super.onResume(); 246 final boolean isShortcutImeEnabled = SubtypeSwitcher.getInstance().isShortcutImeEnabled(); 247 if (!isShortcutImeEnabled) { 248 getPreferenceScreen().removePreference(mVoiceInputKeyPreference); 249 } 250 final SharedPreferences prefs = getPreferenceManager().getSharedPreferences(); 251 final CheckBoxPreference showSetupWizardIcon = 252 (CheckBoxPreference)findPreference(Settings.PREF_SHOW_SETUP_WIZARD_ICON); 253 if (showSetupWizardIcon != null) { 254 showSetupWizardIcon.setChecked(Settings.readShowSetupWizardIcon(prefs, getActivity())); 255 } 256 updateShowCorrectionSuggestionsSummary(); 257 updateKeyPreviewPopupDelaySummary(); 258 updateColorSchemeSummary(prefs, getResources()); 259 updateCustomInputStylesSummary(); 260 } 261 262 @Override 263 public void onDestroy() { 264 getPreferenceManager().getSharedPreferences().unregisterOnSharedPreferenceChangeListener( 265 this); 266 super.onDestroy(); 267 } 268 269 @Override 270 public void onSharedPreferenceChanged(final SharedPreferences prefs, final String key) { 271 final Activity activity = getActivity(); 272 if (activity == null) { 273 // TODO: Introduce a static function to register this class and ensure that 274 // onCreate must be called before "onSharedPreferenceChanged" is called. 275 Log.w(TAG, "onSharedPreferenceChanged called before activity starts."); 276 return; 277 } 278 (new BackupManager(activity)).dataChanged(); 279 final Resources res = getResources(); 280 if (key.equals(Settings.PREF_POPUP_ON)) { 281 setPreferenceEnabled(Settings.PREF_KEY_PREVIEW_POPUP_DISMISS_DELAY, 282 Settings.readKeyPreviewPopupEnabled(prefs, res)); 283 } else if (key.equals(Settings.PREF_SHOW_LANGUAGE_SWITCH_KEY)) { 284 setPreferenceEnabled(Settings.PREF_INCLUDE_OTHER_IMES_IN_LANGUAGE_SWITCH_LIST, 285 Settings.readShowsLanguageSwitchKey(prefs)); 286 } else if (key.equals(Settings.PREF_SHOW_SETUP_WIZARD_ICON)) { 287 LauncherIconVisibilityManager.updateSetupWizardIconVisibility(getActivity()); 288 } 289 ensureConsistencyOfAutoCorrectionSettings(); 290 updateShowCorrectionSuggestionsSummary(); 291 updateKeyPreviewPopupDelaySummary(); 292 updateColorSchemeSummary(prefs, res); 293 refreshEnablingsOfKeypressSoundAndVibrationSettings(prefs, getResources()); 294 } 295 296 private void ensureConsistencyOfAutoCorrectionSettings() { 297 final String autoCorrectionOff = getResources().getString( 298 R.string.auto_correction_threshold_mode_index_off); 299 final String currentSetting = mAutoCorrectionThresholdPreference.getValue(); 300 mBigramPrediction.setEnabled(!currentSetting.equals(autoCorrectionOff)); 301 } 302 303 private void updateShowCorrectionSuggestionsSummary() { 304 mShowCorrectionSuggestionsPreference.setSummary( 305 getResources().getStringArray(R.array.prefs_suggestion_visibilities) 306 [mShowCorrectionSuggestionsPreference.findIndexOfValue( 307 mShowCorrectionSuggestionsPreference.getValue())]); 308 } 309 310 private void updateColorSchemeSummary(final SharedPreferences prefs, final Resources res) { 311 // Because the "%s" summary trick of {@link ListPreference} doesn't work properly before 312 // KitKat, we need to update the summary by code. 313 final Preference preference = findPreference(Settings.PREF_KEYBOARD_LAYOUT); 314 if (!(preference instanceof ListPreference)) { 315 Log.w(TAG, "Can't find Keyboard Color Scheme preference"); 316 return; 317 } 318 final ListPreference colorSchemePreference = (ListPreference)preference; 319 final int themeIndex = Settings.readKeyboardThemeIndex(prefs, res); 320 int entryIndex = colorSchemePreference.findIndexOfValue(Integer.toString(themeIndex)); 321 if (entryIndex < 0) { 322 final int defaultThemeIndex = Settings.resetAndGetDefaultKeyboardThemeIndex(prefs, res); 323 entryIndex = colorSchemePreference.findIndexOfValue( 324 Integer.toString(defaultThemeIndex)); 325 } 326 colorSchemePreference.setSummary(colorSchemePreference.getEntries()[entryIndex]); 327 } 328 329 private void updateCustomInputStylesSummary() { 330 final PreferenceScreen customInputStyles = 331 (PreferenceScreen)findPreference(Settings.PREF_CUSTOM_INPUT_STYLES); 332 final SharedPreferences prefs = getPreferenceManager().getSharedPreferences(); 333 final Resources res = getResources(); 334 final String prefSubtype = Settings.readPrefAdditionalSubtypes(prefs, res); 335 final InputMethodSubtype[] subtypes = 336 AdditionalSubtypeUtils.createAdditionalSubtypesArray(prefSubtype); 337 final StringBuilder styles = new StringBuilder(); 338 for (final InputMethodSubtype subtype : subtypes) { 339 if (styles.length() > 0) styles.append(", "); 340 styles.append(SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(subtype)); 341 } 342 customInputStyles.setSummary(styles); 343 } 344 345 private void updateKeyPreviewPopupDelaySummary() { 346 final ListPreference lp = mKeyPreviewPopupDismissDelay; 347 final CharSequence[] entries = lp.getEntries(); 348 if (entries == null || entries.length <= 0) return; 349 lp.setSummary(entries[lp.findIndexOfValue(lp.getValue())]); 350 } 351 352 private void refreshEnablingsOfKeypressSoundAndVibrationSettings( 353 final SharedPreferences sp, final Resources res) { 354 setPreferenceEnabled(Settings.PREF_VIBRATION_DURATION_SETTINGS, 355 Settings.readVibrationEnabled(sp, res)); 356 setPreferenceEnabled(Settings.PREF_KEYPRESS_SOUND_VOLUME, 357 Settings.readKeypressSoundEnabled(sp, res)); 358 } 359 360 private void setupKeypressVibrationDurationSettings(final SharedPreferences sp, 361 final Resources res) { 362 final SeekBarDialogPreference pref = (SeekBarDialogPreference)findPreference( 363 Settings.PREF_VIBRATION_DURATION_SETTINGS); 364 if (pref == null) { 365 return; 366 } 367 pref.setInterface(new SeekBarDialogPreference.ValueProxy() { 368 @Override 369 public void writeValue(final int value, final String key) { 370 sp.edit().putInt(key, value).apply(); 371 } 372 373 @Override 374 public void writeDefaultValue(final String key) { 375 sp.edit().remove(key).apply(); 376 } 377 378 @Override 379 public int readValue(final String key) { 380 return Settings.readKeypressVibrationDuration(sp, res); 381 } 382 383 @Override 384 public int readDefaultValue(final String key) { 385 return Settings.readDefaultKeypressVibrationDuration(res); 386 } 387 388 @Override 389 public void feedbackValue(final int value) { 390 AudioAndHapticFeedbackManager.getInstance().vibrate(value); 391 } 392 393 @Override 394 public String getValueText(final int value) { 395 if (value < 0) { 396 return res.getString(R.string.settings_system_default); 397 } 398 return res.getString(R.string.abbreviation_unit_milliseconds, value); 399 } 400 }); 401 } 402 403 private void setupKeyLongpressTimeoutSettings(final SharedPreferences sp, 404 final Resources res) { 405 final SeekBarDialogPreference pref = (SeekBarDialogPreference)findPreference( 406 Settings.PREF_KEY_LONGPRESS_TIMEOUT); 407 if (pref == null) { 408 return; 409 } 410 pref.setInterface(new SeekBarDialogPreference.ValueProxy() { 411 @Override 412 public void writeValue(final int value, final String key) { 413 sp.edit().putInt(key, value).apply(); 414 } 415 416 @Override 417 public void writeDefaultValue(final String key) { 418 sp.edit().remove(key).apply(); 419 } 420 421 @Override 422 public int readValue(final String key) { 423 return Settings.readKeyLongpressTimeout(sp, res); 424 } 425 426 @Override 427 public int readDefaultValue(final String key) { 428 return Settings.readDefaultKeyLongpressTimeout(res); 429 } 430 431 @Override 432 public String getValueText(final int value) { 433 return res.getString(R.string.abbreviation_unit_milliseconds, value); 434 } 435 436 @Override 437 public void feedbackValue(final int value) {} 438 }); 439 } 440 441 private void setupKeypressSoundVolumeSettings(final SharedPreferences sp, final Resources res) { 442 final SeekBarDialogPreference pref = (SeekBarDialogPreference)findPreference( 443 Settings.PREF_KEYPRESS_SOUND_VOLUME); 444 if (pref == null) { 445 return; 446 } 447 final AudioManager am = (AudioManager)getActivity().getSystemService(Context.AUDIO_SERVICE); 448 pref.setInterface(new SeekBarDialogPreference.ValueProxy() { 449 private static final float PERCENTAGE_FLOAT = 100.0f; 450 451 private float getValueFromPercentage(final int percentage) { 452 return percentage / PERCENTAGE_FLOAT; 453 } 454 455 private int getPercentageFromValue(final float floatValue) { 456 return (int)(floatValue * PERCENTAGE_FLOAT); 457 } 458 459 @Override 460 public void writeValue(final int value, final String key) { 461 sp.edit().putFloat(key, getValueFromPercentage(value)).apply(); 462 } 463 464 @Override 465 public void writeDefaultValue(final String key) { 466 sp.edit().remove(key).apply(); 467 } 468 469 @Override 470 public int readValue(final String key) { 471 return getPercentageFromValue(Settings.readKeypressSoundVolume(sp, res)); 472 } 473 474 @Override 475 public int readDefaultValue(final String key) { 476 return getPercentageFromValue(Settings.readDefaultKeypressSoundVolume(res)); 477 } 478 479 @Override 480 public String getValueText(final int value) { 481 if (value < 0) { 482 return res.getString(R.string.settings_system_default); 483 } 484 return Integer.toString(value); 485 } 486 487 @Override 488 public void feedbackValue(final int value) { 489 am.playSoundEffect( 490 AudioManager.FX_KEYPRESS_STANDARD, getValueFromPercentage(value)); 491 } 492 }); 493 } 494 495 private void overwriteUserDictionaryPreference(Preference userDictionaryPreference) { 496 final Activity activity = getActivity(); 497 final TreeSet<String> localeList = UserDictionaryList.getUserDictionaryLocalesSet(activity); 498 if (null == localeList) { 499 // The locale list is null if and only if the user dictionary service is 500 // not present or disabled. In this case we need to remove the preference. 501 getPreferenceScreen().removePreference(userDictionaryPreference); 502 } else if (localeList.size() <= 1) { 503 userDictionaryPreference.setFragment(UserDictionarySettings.class.getName()); 504 // If the size of localeList is 0, we don't set the locale parameter in the 505 // extras. This will be interpreted by the UserDictionarySettings class as 506 // meaning "the current locale". 507 // Note that with the current code for UserDictionaryList#getUserDictionaryLocalesSet() 508 // the locale list always has at least one element, since it always includes the current 509 // locale explicitly. @see UserDictionaryList.getUserDictionaryLocalesSet(). 510 if (localeList.size() == 1) { 511 final String locale = (String)localeList.toArray()[0]; 512 userDictionaryPreference.getExtras().putString("locale", locale); 513 } 514 } else { 515 userDictionaryPreference.setFragment(UserDictionaryList.class.getName()); 516 } 517 } 518 } 519