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 updateCustomInputStylesSummary(); 259 } 260 261 @Override 262 public void onDestroy() { 263 getPreferenceManager().getSharedPreferences().unregisterOnSharedPreferenceChangeListener( 264 this); 265 super.onDestroy(); 266 } 267 268 @Override 269 public void onSharedPreferenceChanged(final SharedPreferences prefs, final String key) { 270 final Activity activity = getActivity(); 271 if (activity == null) { 272 // TODO: Introduce a static function to register this class and ensure that 273 // onCreate must be called before "onSharedPreferenceChanged" is called. 274 Log.w(TAG, "onSharedPreferenceChanged called before activity starts."); 275 return; 276 } 277 (new BackupManager(activity)).dataChanged(); 278 final Resources res = getResources(); 279 if (key.equals(Settings.PREF_POPUP_ON)) { 280 setPreferenceEnabled(Settings.PREF_KEY_PREVIEW_POPUP_DISMISS_DELAY, 281 Settings.readKeyPreviewPopupEnabled(prefs, res)); 282 } else if (key.equals(Settings.PREF_SHOW_LANGUAGE_SWITCH_KEY)) { 283 setPreferenceEnabled(Settings.PREF_INCLUDE_OTHER_IMES_IN_LANGUAGE_SWITCH_LIST, 284 Settings.readShowsLanguageSwitchKey(prefs)); 285 } else if (key.equals(Settings.PREF_SHOW_SETUP_WIZARD_ICON)) { 286 LauncherIconVisibilityManager.updateSetupWizardIconVisibility(getActivity()); 287 } 288 ensureConsistencyOfAutoCorrectionSettings(); 289 updateShowCorrectionSuggestionsSummary(); 290 updateKeyPreviewPopupDelaySummary(); 291 refreshEnablingsOfKeypressSoundAndVibrationSettings(prefs, getResources()); 292 } 293 294 private void ensureConsistencyOfAutoCorrectionSettings() { 295 final String autoCorrectionOff = getResources().getString( 296 R.string.auto_correction_threshold_mode_index_off); 297 final String currentSetting = mAutoCorrectionThresholdPreference.getValue(); 298 mBigramPrediction.setEnabled(!currentSetting.equals(autoCorrectionOff)); 299 } 300 301 private void updateShowCorrectionSuggestionsSummary() { 302 mShowCorrectionSuggestionsPreference.setSummary( 303 getResources().getStringArray(R.array.prefs_suggestion_visibilities) 304 [mShowCorrectionSuggestionsPreference.findIndexOfValue( 305 mShowCorrectionSuggestionsPreference.getValue())]); 306 } 307 308 private void updateCustomInputStylesSummary() { 309 final PreferenceScreen customInputStyles = 310 (PreferenceScreen)findPreference(Settings.PREF_CUSTOM_INPUT_STYLES); 311 final SharedPreferences prefs = getPreferenceManager().getSharedPreferences(); 312 final Resources res = getResources(); 313 final String prefSubtype = Settings.readPrefAdditionalSubtypes(prefs, res); 314 final InputMethodSubtype[] subtypes = 315 AdditionalSubtypeUtils.createAdditionalSubtypesArray(prefSubtype); 316 final StringBuilder styles = new StringBuilder(); 317 for (final InputMethodSubtype subtype : subtypes) { 318 if (styles.length() > 0) styles.append(", "); 319 styles.append(SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(subtype)); 320 } 321 customInputStyles.setSummary(styles); 322 } 323 324 private void updateKeyPreviewPopupDelaySummary() { 325 final ListPreference lp = mKeyPreviewPopupDismissDelay; 326 final CharSequence[] entries = lp.getEntries(); 327 if (entries == null || entries.length <= 0) return; 328 lp.setSummary(entries[lp.findIndexOfValue(lp.getValue())]); 329 } 330 331 private void refreshEnablingsOfKeypressSoundAndVibrationSettings( 332 final SharedPreferences sp, final Resources res) { 333 setPreferenceEnabled(Settings.PREF_VIBRATION_DURATION_SETTINGS, 334 Settings.readVibrationEnabled(sp, res)); 335 setPreferenceEnabled(Settings.PREF_KEYPRESS_SOUND_VOLUME, 336 Settings.readKeypressSoundEnabled(sp, res)); 337 } 338 339 private void setupKeypressVibrationDurationSettings(final SharedPreferences sp, 340 final Resources res) { 341 final SeekBarDialogPreference pref = (SeekBarDialogPreference)findPreference( 342 Settings.PREF_VIBRATION_DURATION_SETTINGS); 343 if (pref == null) { 344 return; 345 } 346 pref.setInterface(new SeekBarDialogPreference.ValueProxy() { 347 @Override 348 public void writeValue(final int value, final String key) { 349 sp.edit().putInt(key, value).apply(); 350 } 351 352 @Override 353 public void writeDefaultValue(final String key) { 354 sp.edit().remove(key).apply(); 355 } 356 357 @Override 358 public int readValue(final String key) { 359 return Settings.readKeypressVibrationDuration(sp, res); 360 } 361 362 @Override 363 public int readDefaultValue(final String key) { 364 return Settings.readDefaultKeypressVibrationDuration(res); 365 } 366 367 @Override 368 public void feedbackValue(final int value) { 369 AudioAndHapticFeedbackManager.getInstance().vibrate(value); 370 } 371 372 @Override 373 public String getValueText(final int value) { 374 if (value < 0) { 375 return res.getString(R.string.settings_system_default); 376 } 377 return res.getString(R.string.abbreviation_unit_milliseconds, value); 378 } 379 }); 380 } 381 382 private void setupKeyLongpressTimeoutSettings(final SharedPreferences sp, 383 final Resources res) { 384 final SeekBarDialogPreference pref = (SeekBarDialogPreference)findPreference( 385 Settings.PREF_KEY_LONGPRESS_TIMEOUT); 386 if (pref == null) { 387 return; 388 } 389 pref.setInterface(new SeekBarDialogPreference.ValueProxy() { 390 @Override 391 public void writeValue(final int value, final String key) { 392 sp.edit().putInt(key, value).apply(); 393 } 394 395 @Override 396 public void writeDefaultValue(final String key) { 397 sp.edit().remove(key).apply(); 398 } 399 400 @Override 401 public int readValue(final String key) { 402 return Settings.readKeyLongpressTimeout(sp, res); 403 } 404 405 @Override 406 public int readDefaultValue(final String key) { 407 return Settings.readDefaultKeyLongpressTimeout(res); 408 } 409 410 @Override 411 public String getValueText(final int value) { 412 return res.getString(R.string.abbreviation_unit_milliseconds, value); 413 } 414 415 @Override 416 public void feedbackValue(final int value) {} 417 }); 418 } 419 420 private void setupKeypressSoundVolumeSettings(final SharedPreferences sp, final Resources res) { 421 final SeekBarDialogPreference pref = (SeekBarDialogPreference)findPreference( 422 Settings.PREF_KEYPRESS_SOUND_VOLUME); 423 if (pref == null) { 424 return; 425 } 426 final AudioManager am = (AudioManager)getActivity().getSystemService(Context.AUDIO_SERVICE); 427 pref.setInterface(new SeekBarDialogPreference.ValueProxy() { 428 private static final float PERCENTAGE_FLOAT = 100.0f; 429 430 private float getValueFromPercentage(final int percentage) { 431 return percentage / PERCENTAGE_FLOAT; 432 } 433 434 private int getPercentageFromValue(final float floatValue) { 435 return (int)(floatValue * PERCENTAGE_FLOAT); 436 } 437 438 @Override 439 public void writeValue(final int value, final String key) { 440 sp.edit().putFloat(key, getValueFromPercentage(value)).apply(); 441 } 442 443 @Override 444 public void writeDefaultValue(final String key) { 445 sp.edit().remove(key).apply(); 446 } 447 448 @Override 449 public int readValue(final String key) { 450 return getPercentageFromValue(Settings.readKeypressSoundVolume(sp, res)); 451 } 452 453 @Override 454 public int readDefaultValue(final String key) { 455 return getPercentageFromValue(Settings.readDefaultKeypressSoundVolume(res)); 456 } 457 458 @Override 459 public String getValueText(final int value) { 460 if (value < 0) { 461 return res.getString(R.string.settings_system_default); 462 } 463 return Integer.toString(value); 464 } 465 466 @Override 467 public void feedbackValue(final int value) { 468 am.playSoundEffect( 469 AudioManager.FX_KEYPRESS_STANDARD, getValueFromPercentage(value)); 470 } 471 }); 472 } 473 474 private void overwriteUserDictionaryPreference(Preference userDictionaryPreference) { 475 final Activity activity = getActivity(); 476 final TreeSet<String> localeList = UserDictionaryList.getUserDictionaryLocalesSet(activity); 477 if (null == localeList) { 478 // The locale list is null if and only if the user dictionary service is 479 // not present or disabled. In this case we need to remove the preference. 480 getPreferenceScreen().removePreference(userDictionaryPreference); 481 } else if (localeList.size() <= 1) { 482 userDictionaryPreference.setFragment(UserDictionarySettings.class.getName()); 483 // If the size of localeList is 0, we don't set the locale parameter in the 484 // extras. This will be interpreted by the UserDictionarySettings class as 485 // meaning "the current locale". 486 // Note that with the current code for UserDictionaryList#getUserDictionaryLocalesSet() 487 // the locale list always has at least one element, since it always includes the current 488 // locale explicitly. @see UserDictionaryList.getUserDictionaryLocalesSet(). 489 if (localeList.size() == 1) { 490 final String locale = (String)localeList.toArray()[0]; 491 userDictionaryPreference.getExtras().putString("locale", locale); 492 } 493 } else { 494 userDictionaryPreference.setFragment(UserDictionaryList.class.getName()); 495 } 496 } 497 } 498