1 /* 2 * Copyright (C) 2011 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.settings.tts; 18 19 import android.support.v7.preference.ListPreference; 20 import android.app.AlertDialog; 21 import android.content.ActivityNotFoundException; 22 import android.content.ContentResolver; 23 import android.content.Intent; 24 import android.os.Bundle; 25 import android.speech.tts.TextToSpeech; 26 import android.speech.tts.TextToSpeech.EngineInfo; 27 import android.speech.tts.TtsEngines; 28 import android.speech.tts.UtteranceProgressListener; 29 import android.support.v7.preference.Preference; 30 import android.text.TextUtils; 31 import android.util.Log; 32 import android.util.Pair; 33 import android.view.View; 34 import java.util.Comparator; 35 import java.util.Collections; 36 import com.android.settings.applications.LayoutPreference; 37 38 import com.android.internal.logging.nano.MetricsProto.MetricsEvent; 39 import com.android.settings.SeekBarPreference; 40 import com.android.settings.R; 41 import com.android.settings.SettingsActivity; 42 import com.android.settings.SettingsPreferenceFragment; 43 import com.android.settings.widget.GearPreference; 44 import android.widget.Button; 45 46 import java.util.ArrayList; 47 import java.util.HashMap; 48 import java.util.List; 49 import java.util.Locale; 50 import java.util.MissingResourceException; 51 import java.util.Objects; 52 import java.util.Set; 53 54 import static android.provider.Settings.Secure.TTS_DEFAULT_PITCH; 55 import static android.provider.Settings.Secure.TTS_DEFAULT_RATE; 56 import static android.provider.Settings.Secure.TTS_DEFAULT_SYNTH; 57 58 public class TextToSpeechSettings extends SettingsPreferenceFragment 59 implements Preference.OnPreferenceChangeListener, 60 View.OnClickListener, 61 GearPreference.OnGearClickListener { 62 63 private static final String STATE_KEY_LOCALE_ENTRIES = "locale_entries"; 64 private static final String STATE_KEY_LOCALE_ENTRY_VALUES = "locale_entry_values"; 65 private static final String STATE_KEY_LOCALE_VALUE = "locale_value"; 66 67 private static final String TAG = "TextToSpeechSettings"; 68 private static final boolean DBG = false; 69 70 /** Preference key for the TTS pitch selection slider. */ 71 private static final String KEY_DEFAULT_PITCH = "tts_default_pitch"; 72 73 /** Preference key for the TTS rate selection slider. */ 74 private static final String KEY_DEFAULT_RATE = "tts_default_rate"; 75 76 /** Engine picker. */ 77 private static final String KEY_TTS_ENGINE_PREFERENCE = "tts_engine_preference"; 78 79 /** Locale picker. */ 80 private static final String KEY_ENGINE_LOCALE = "tts_default_lang"; 81 82 /** Play/Reset buttons container. */ 83 private static final String KEY_ACTION_BUTTONS = "action_buttons"; 84 85 /** 86 * These look like birth years, but they aren't mine. I'm much younger than this. 87 */ 88 private static final int GET_SAMPLE_TEXT = 1983; 89 private static final int VOICE_DATA_INTEGRITY_CHECK = 1977; 90 91 /** 92 * Speech rate value. This value should be kept in sync with the max value set in tts_settings 93 * xml. 94 */ 95 private static final int MAX_SPEECH_RATE = 600; 96 97 private static final int MIN_SPEECH_RATE = 10; 98 99 /** 100 * Speech pitch value. TTS pitch value varies from 25 to 400, where 100 is the value for normal 101 * pitch. The max pitch value is set to 400, based on feedback from users and the GoogleTTS 102 * pitch variation range. The range for pitch is not set in stone and should be readjusted based 103 * on user need. This value should be kept in sync with the max value set in tts_settings xml. 104 */ 105 private static final int MAX_SPEECH_PITCH = 400; 106 107 private static final int MIN_SPEECH_PITCH = 25; 108 109 private SeekBarPreference mDefaultPitchPref; 110 private SeekBarPreference mDefaultRatePref; 111 private Button mResetButton; 112 private Button mPlayButton; 113 private LayoutPreference mActionButtons; 114 115 private int mDefaultPitch = TextToSpeech.Engine.DEFAULT_PITCH; 116 private int mDefaultRate = TextToSpeech.Engine.DEFAULT_RATE; 117 118 private int mSelectedLocaleIndex = -1; 119 120 /** The currently selected engine. */ 121 private String mCurrentEngine; 122 123 private TextToSpeech mTts = null; 124 private TtsEngines mEnginesHelper = null; 125 126 private String mSampleText = null; 127 128 private ListPreference mLocalePreference; 129 private Preference mInstallVoicesPreference; 130 131 /** 132 * Default locale used by selected TTS engine, null if not connected to any engine. 133 */ 134 private Locale mCurrentDefaultLocale; 135 136 /** 137 * List of available locals of selected TTS engine, as returned by 138 * {@link TextToSpeech.Engine#ACTION_CHECK_TTS_DATA} activity. If empty, then activity 139 * was not yet called. 140 */ 141 private List<String> mAvailableStrLocals; 142 143 /** 144 * The initialization listener used when we are initalizing the settings 145 * screen for the first time (as opposed to when a user changes his choice 146 * of engine). 147 */ 148 private final TextToSpeech.OnInitListener mInitListener = new TextToSpeech.OnInitListener() { 149 @Override 150 public void onInit(int status) { 151 onInitEngine(status); 152 } 153 }; 154 155 @Override getMetricsCategory()156 public int getMetricsCategory() { 157 return MetricsEvent.TTS_TEXT_TO_SPEECH; 158 } 159 160 @Override onCreate(Bundle savedInstanceState)161 public void onCreate(Bundle savedInstanceState) { 162 super.onCreate(savedInstanceState); 163 addPreferencesFromResource(R.xml.tts_settings); 164 165 getActivity().setVolumeControlStream(TextToSpeech.Engine.DEFAULT_STREAM); 166 167 mEnginesHelper = new TtsEngines(getActivity().getApplicationContext()); 168 169 mLocalePreference = (ListPreference) findPreference(KEY_ENGINE_LOCALE); 170 mLocalePreference.setOnPreferenceChangeListener(this); 171 172 mDefaultPitchPref = (SeekBarPreference) findPreference(KEY_DEFAULT_PITCH); 173 mDefaultRatePref = (SeekBarPreference) findPreference(KEY_DEFAULT_RATE); 174 175 mActionButtons = (LayoutPreference) findPreference(KEY_ACTION_BUTTONS); 176 mPlayButton = (Button) mActionButtons.findViewById(R.id.tts_play_button); 177 mPlayButton.setOnClickListener(this); 178 mPlayButton.setEnabled(false); 179 mResetButton = (Button) mActionButtons.findViewById(R.id.tts_reset_button); 180 mResetButton.setOnClickListener(this); 181 182 if (savedInstanceState == null) { 183 mLocalePreference.setEnabled(false); 184 mLocalePreference.setEntries(new CharSequence[0]); 185 mLocalePreference.setEntryValues(new CharSequence[0]); 186 } else { 187 // Repopulate mLocalePreference with saved state. Will be updated later with 188 // up-to-date values when checkTtsData() calls back with results. 189 final CharSequence[] entries = 190 savedInstanceState.getCharSequenceArray(STATE_KEY_LOCALE_ENTRIES); 191 final CharSequence[] entryValues = 192 savedInstanceState.getCharSequenceArray(STATE_KEY_LOCALE_ENTRY_VALUES); 193 final CharSequence value = savedInstanceState.getCharSequence(STATE_KEY_LOCALE_VALUE); 194 195 mLocalePreference.setEntries(entries); 196 mLocalePreference.setEntryValues(entryValues); 197 mLocalePreference.setValue(value != null ? value.toString() : null); 198 mLocalePreference.setEnabled(entries.length > 0); 199 } 200 201 mTts = new TextToSpeech(getActivity().getApplicationContext(), mInitListener); 202 203 setTtsUtteranceProgressListener(); 204 initSettings(); 205 206 // Prevent restarting the TTS connection on rotation 207 setRetainInstance(true); 208 } 209 210 @Override onResume()211 public void onResume() { 212 super.onResume(); 213 214 if (mTts == null || mCurrentDefaultLocale == null) { 215 return; 216 } 217 if (!mTts.getDefaultEngine().equals(mTts.getCurrentEngine())) { 218 try { 219 mTts.shutdown(); 220 mTts = null; 221 } catch (Exception e) { 222 Log.e(TAG, "Error shutting down TTS engine" + e); 223 } 224 mTts = new TextToSpeech(getActivity().getApplicationContext(), mInitListener); 225 setTtsUtteranceProgressListener(); 226 initSettings(); 227 } else { 228 // Do set pitch correctly after it may have changed, and unlike speed, it doesn't change 229 // immediately. 230 final ContentResolver resolver = getContentResolver(); 231 mTts.setPitch(android.provider.Settings.Secure.getInt(resolver, TTS_DEFAULT_PITCH, TextToSpeech.Engine.DEFAULT_PITCH)/100.0f); 232 } 233 234 Locale ttsDefaultLocale = mTts.getDefaultLanguage(); 235 if (mCurrentDefaultLocale != null && !mCurrentDefaultLocale.equals(ttsDefaultLocale)) { 236 updateWidgetState(false); 237 checkDefaultLocale(); 238 } 239 } 240 setTtsUtteranceProgressListener()241 private void setTtsUtteranceProgressListener() { 242 if (mTts == null) { 243 return; 244 } 245 mTts.setOnUtteranceProgressListener(new UtteranceProgressListener() { 246 @Override 247 public void onStart(String utteranceId) {} 248 249 @Override 250 public void onDone(String utteranceId) {} 251 252 @Override 253 public void onError(String utteranceId) { 254 Log.e(TAG, "Error while trying to synthesize sample text"); 255 } 256 }); 257 } 258 259 @Override onDestroy()260 public void onDestroy() { 261 super.onDestroy(); 262 if (mTts != null) { 263 mTts.shutdown(); 264 mTts = null; 265 } 266 } 267 268 @Override onSaveInstanceState(Bundle outState)269 public void onSaveInstanceState(Bundle outState) { 270 super.onSaveInstanceState(outState); 271 272 // Save the mLocalePreference values, so we can repopulate it with entries. 273 outState.putCharSequenceArray(STATE_KEY_LOCALE_ENTRIES, 274 mLocalePreference.getEntries()); 275 outState.putCharSequenceArray(STATE_KEY_LOCALE_ENTRY_VALUES, 276 mLocalePreference.getEntryValues()); 277 outState.putCharSequence(STATE_KEY_LOCALE_VALUE, 278 mLocalePreference.getValue()); 279 } 280 initSettings()281 private void initSettings() { 282 final ContentResolver resolver = getContentResolver(); 283 284 // Set up the default rate and pitch. 285 mDefaultRate = 286 android.provider.Settings.Secure.getInt( 287 resolver, TTS_DEFAULT_RATE, TextToSpeech.Engine.DEFAULT_RATE); 288 mDefaultPitch = 289 android.provider.Settings.Secure.getInt( 290 resolver, TTS_DEFAULT_PITCH, TextToSpeech.Engine.DEFAULT_PITCH); 291 292 mDefaultRatePref.setProgress(getSeekBarProgressFromValue(KEY_DEFAULT_RATE, mDefaultRate)); 293 mDefaultRatePref.setOnPreferenceChangeListener(this); 294 mDefaultRatePref.setMax(getSeekBarProgressFromValue(KEY_DEFAULT_RATE, MAX_SPEECH_RATE)); 295 296 mDefaultPitchPref.setProgress( 297 getSeekBarProgressFromValue(KEY_DEFAULT_PITCH, mDefaultPitch)); 298 mDefaultPitchPref.setOnPreferenceChangeListener(this); 299 mDefaultPitchPref.setMax(getSeekBarProgressFromValue(KEY_DEFAULT_PITCH, MAX_SPEECH_PITCH)); 300 301 if (mTts != null) { 302 mCurrentEngine = mTts.getCurrentEngine(); 303 mTts.setSpeechRate(mDefaultRate / 100.0f); 304 mTts.setPitch(mDefaultPitch / 100.0f); 305 } 306 307 SettingsActivity activity = null; 308 if (getActivity() instanceof SettingsActivity) { 309 activity = (SettingsActivity) getActivity(); 310 } else { 311 throw new IllegalStateException("TextToSpeechSettings used outside a " + 312 "Settings"); 313 } 314 315 if (mCurrentEngine != null) { 316 EngineInfo info = mEnginesHelper.getEngineInfo(mCurrentEngine); 317 318 319 320 Preference mEnginePreference = findPreference(KEY_TTS_ENGINE_PREFERENCE); 321 ((GearPreference) mEnginePreference).setOnGearClickListener(this); 322 mEnginePreference.setSummary(info.label); 323 } 324 325 checkVoiceData(mCurrentEngine); 326 } 327 328 /** 329 * The minimum speech pitch/rate value should be > 0 but the minimum value of a seekbar in 330 * android is fixed at 0. Therefore, we increment the seekbar progress with MIN_SPEECH_VALUE so 331 * that the minimum seekbar progress value is MIN_SPEECH_PITCH/RATE. SPEECH_VALUE = 332 * MIN_SPEECH_VALUE + SEEKBAR_PROGRESS 333 */ getValueFromSeekBarProgress(String preferenceKey, int progress)334 private int getValueFromSeekBarProgress(String preferenceKey, int progress) { 335 if (preferenceKey.equals(KEY_DEFAULT_RATE)) { 336 return MIN_SPEECH_RATE + progress; 337 } else if (preferenceKey.equals(KEY_DEFAULT_PITCH)) { 338 return MIN_SPEECH_PITCH + progress; 339 } 340 return progress; 341 } 342 343 /** 344 * Since we are appending the MIN_SPEECH value to the speech seekbar progress, the speech 345 * seekbar progress should be set to (speechValue - MIN_SPEECH value). 346 */ getSeekBarProgressFromValue(String preferenceKey, int value)347 private int getSeekBarProgressFromValue(String preferenceKey, int value) { 348 if (preferenceKey.equals(KEY_DEFAULT_RATE)) { 349 return value - MIN_SPEECH_RATE; 350 } else if (preferenceKey.equals(KEY_DEFAULT_PITCH)) { 351 return value - MIN_SPEECH_PITCH; 352 } 353 return value; 354 } 355 356 /** Called when the TTS engine is initialized. */ onInitEngine(int status)357 public void onInitEngine(int status) { 358 if (status == TextToSpeech.SUCCESS) { 359 if (DBG) Log.d(TAG, "TTS engine for settings screen initialized."); 360 checkDefaultLocale(); 361 getActivity() 362 .runOnUiThread( 363 new Runnable() { 364 @Override 365 public void run() { 366 mLocalePreference.setEnabled(true); 367 } 368 }); 369 } else { 370 if (DBG) Log.d(TAG, "TTS engine for settings screen failed to initialize successfully."); 371 updateWidgetState(false); 372 } 373 } 374 checkDefaultLocale()375 private void checkDefaultLocale() { 376 Locale defaultLocale = mTts.getDefaultLanguage(); 377 if (defaultLocale == null) { 378 Log.e(TAG, "Failed to get default language from engine " + mCurrentEngine); 379 updateWidgetState(false); 380 return; 381 } 382 383 // ISO-3166 alpha 3 country codes are out of spec. If we won't normalize, 384 // we may end up with English (USA)and German (DEU). 385 final Locale oldDefaultLocale = mCurrentDefaultLocale; 386 mCurrentDefaultLocale = mEnginesHelper.parseLocaleString(defaultLocale.toString()); 387 if (!Objects.equals(oldDefaultLocale, mCurrentDefaultLocale)) { 388 mSampleText = null; 389 } 390 391 int defaultAvailable = mTts.setLanguage(defaultLocale); 392 if (evaluateDefaultLocale() && mSampleText == null) { 393 getSampleText(); 394 } 395 } 396 evaluateDefaultLocale()397 private boolean evaluateDefaultLocale() { 398 // Check if we are connected to the engine, and CHECK_VOICE_DATA returned list 399 // of available languages. 400 if (mCurrentDefaultLocale == null || mAvailableStrLocals == null) { 401 return false; 402 } 403 404 boolean notInAvailableLangauges = true; 405 try { 406 // Check if language is listed in CheckVoices Action result as available voice. 407 String defaultLocaleStr = mCurrentDefaultLocale.getISO3Language(); 408 if (!TextUtils.isEmpty(mCurrentDefaultLocale.getISO3Country())) { 409 defaultLocaleStr += "-" + mCurrentDefaultLocale.getISO3Country(); 410 } 411 if (!TextUtils.isEmpty(mCurrentDefaultLocale.getVariant())) { 412 defaultLocaleStr += "-" + mCurrentDefaultLocale.getVariant(); 413 } 414 415 for (String loc : mAvailableStrLocals) { 416 if (loc.equalsIgnoreCase(defaultLocaleStr)) { 417 notInAvailableLangauges = false; 418 break; 419 } 420 } 421 } catch (MissingResourceException e) { 422 if (DBG) Log.wtf(TAG, "MissingResourceException", e); 423 updateWidgetState(false); 424 return false; 425 } 426 427 int defaultAvailable = mTts.setLanguage(mCurrentDefaultLocale); 428 if (defaultAvailable == TextToSpeech.LANG_NOT_SUPPORTED || 429 defaultAvailable == TextToSpeech.LANG_MISSING_DATA || 430 notInAvailableLangauges) { 431 if (DBG) Log.d(TAG, "Default locale for this TTS engine is not supported."); 432 updateWidgetState(false); 433 return false; 434 } else { 435 updateWidgetState(true); 436 return true; 437 } 438 } 439 440 /** 441 * Ask the current default engine to return a string of sample text to be 442 * spoken to the user. 443 */ getSampleText()444 private void getSampleText() { 445 String currentEngine = mTts.getCurrentEngine(); 446 447 if (TextUtils.isEmpty(currentEngine)) currentEngine = mTts.getDefaultEngine(); 448 449 // TODO: This is currently a hidden private API. The intent extras 450 // and the intent action should be made public if we intend to make this 451 // a public API. We fall back to using a canned set of strings if this 452 // doesn't work. 453 Intent intent = new Intent(TextToSpeech.Engine.ACTION_GET_SAMPLE_TEXT); 454 455 intent.putExtra("language", mCurrentDefaultLocale.getLanguage()); 456 intent.putExtra("country", mCurrentDefaultLocale.getCountry()); 457 intent.putExtra("variant", mCurrentDefaultLocale.getVariant()); 458 intent.setPackage(currentEngine); 459 460 try { 461 if (DBG) Log.d(TAG, "Getting sample text: " + intent.toUri(0)); 462 startActivityForResult(intent, GET_SAMPLE_TEXT); 463 } catch (ActivityNotFoundException ex) { 464 Log.e(TAG, "Failed to get sample text, no activity found for " + intent + ")"); 465 } 466 } 467 468 /** 469 * Called when voice data integrity check returns 470 */ 471 @Override onActivityResult(int requestCode, int resultCode, Intent data)472 public void onActivityResult(int requestCode, int resultCode, Intent data) { 473 if (requestCode == GET_SAMPLE_TEXT) { 474 onSampleTextReceived(resultCode, data); 475 } else if (requestCode == VOICE_DATA_INTEGRITY_CHECK) { 476 onVoiceDataIntegrityCheckDone(data); 477 if (resultCode != TextToSpeech.Engine.CHECK_VOICE_DATA_FAIL) { 478 updateDefaultLocalePref(data); 479 } 480 } 481 } 482 updateDefaultLocalePref(Intent data)483 private void updateDefaultLocalePref(Intent data) { 484 final ArrayList<String> availableLangs = 485 data.getStringArrayListExtra(TextToSpeech.Engine.EXTRA_AVAILABLE_VOICES); 486 487 final ArrayList<String> unavailableLangs = 488 data.getStringArrayListExtra(TextToSpeech.Engine.EXTRA_UNAVAILABLE_VOICES); 489 490 if (availableLangs == null || availableLangs.size() == 0) { 491 mLocalePreference.setEnabled(false); 492 return; 493 } 494 Locale currentLocale = null; 495 if (!mEnginesHelper.isLocaleSetToDefaultForEngine(mTts.getCurrentEngine())) { 496 currentLocale = mEnginesHelper.getLocalePrefForEngine(mTts.getCurrentEngine()); 497 } 498 499 ArrayList<Pair<String, Locale>> entryPairs = 500 new ArrayList<Pair<String, Locale>>(availableLangs.size()); 501 for (int i = 0; i < availableLangs.size(); i++) { 502 Locale locale = mEnginesHelper.parseLocaleString(availableLangs.get(i)); 503 if (locale != null) { 504 entryPairs.add(new Pair<String, Locale>(locale.getDisplayName(), locale)); 505 } 506 } 507 508 // Sort it 509 Collections.sort( 510 entryPairs, 511 new Comparator<Pair<String, Locale>>() { 512 @Override 513 public int compare(Pair<String, Locale> lhs, Pair<String, Locale> rhs) { 514 return lhs.first.compareToIgnoreCase(rhs.first); 515 } 516 }); 517 518 // Get two arrays out of one of pairs 519 mSelectedLocaleIndex = 0; // Will point to the R.string.tts_lang_use_system value 520 CharSequence[] entries = new CharSequence[availableLangs.size() + 1]; 521 CharSequence[] entryValues = new CharSequence[availableLangs.size() + 1]; 522 523 entries[0] = getActivity().getString(R.string.tts_lang_use_system); 524 entryValues[0] = ""; 525 526 int i = 1; 527 for (Pair<String, Locale> entry : entryPairs) { 528 if (entry.second.equals(currentLocale)) { 529 mSelectedLocaleIndex = i; 530 } 531 entries[i] = entry.first; 532 entryValues[i++] = entry.second.toString(); 533 } 534 535 mLocalePreference.setEntries(entries); 536 mLocalePreference.setEntryValues(entryValues); 537 mLocalePreference.setEnabled(true); 538 setLocalePreference(mSelectedLocaleIndex); 539 } 540 541 /** Set entry from entry table in mLocalePreference */ setLocalePreference(int index)542 private void setLocalePreference(int index) { 543 if (index < 0) { 544 mLocalePreference.setValue(""); 545 mLocalePreference.setSummary(R.string.tts_lang_not_selected); 546 } else { 547 mLocalePreference.setValueIndex(index); 548 mLocalePreference.setSummary(mLocalePreference.getEntries()[index]); 549 } 550 } 551 552 getDefaultSampleString()553 private String getDefaultSampleString() { 554 if (mTts != null && mTts.getLanguage() != null) { 555 try { 556 final String currentLang = mTts.getLanguage().getISO3Language(); 557 String[] strings = getActivity().getResources().getStringArray( 558 R.array.tts_demo_strings); 559 String[] langs = getActivity().getResources().getStringArray( 560 R.array.tts_demo_string_langs); 561 562 for (int i = 0; i < strings.length; ++i) { 563 if (langs[i].equals(currentLang)) { 564 return strings[i]; 565 } 566 } 567 } catch (MissingResourceException e) { 568 if (DBG) Log.wtf(TAG, "MissingResourceException", e); 569 // Ignore and fall back to default sample string 570 } 571 } 572 return getString(R.string.tts_default_sample_string); 573 } 574 isNetworkRequiredForSynthesis()575 private boolean isNetworkRequiredForSynthesis() { 576 Set<String> features = mTts.getFeatures(mCurrentDefaultLocale); 577 if (features == null) { 578 return false; 579 } 580 return features.contains(TextToSpeech.Engine.KEY_FEATURE_NETWORK_SYNTHESIS) && 581 !features.contains(TextToSpeech.Engine.KEY_FEATURE_EMBEDDED_SYNTHESIS); 582 } 583 onSampleTextReceived(int resultCode, Intent data)584 private void onSampleTextReceived(int resultCode, Intent data) { 585 String sample = getDefaultSampleString(); 586 587 if (resultCode == TextToSpeech.LANG_AVAILABLE && data != null) { 588 if (data != null && data.getStringExtra("sampleText") != null) { 589 sample = data.getStringExtra("sampleText"); 590 } 591 if (DBG) Log.d(TAG, "Got sample text: " + sample); 592 } else { 593 if (DBG) Log.d(TAG, "Using default sample text :" + sample); 594 } 595 596 mSampleText = sample; 597 if (mSampleText != null) { 598 updateWidgetState(true); 599 } else { 600 Log.e(TAG, "Did not have a sample string for the requested language. Using default"); 601 } 602 } 603 speakSampleText()604 private void speakSampleText() { 605 final boolean networkRequired = isNetworkRequiredForSynthesis(); 606 if (!networkRequired || networkRequired && 607 (mTts.isLanguageAvailable(mCurrentDefaultLocale) >= TextToSpeech.LANG_AVAILABLE)) { 608 HashMap<String, String> params = new HashMap<String, String>(); 609 params.put(TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID, "Sample"); 610 611 mTts.speak(mSampleText, TextToSpeech.QUEUE_FLUSH, params); 612 } else { 613 Log.w(TAG, "Network required for sample synthesis for requested language"); 614 displayNetworkAlert(); 615 } 616 } 617 618 @Override onPreferenceChange(Preference preference, Object objValue)619 public boolean onPreferenceChange(Preference preference, Object objValue) { 620 if (KEY_DEFAULT_RATE.equals(preference.getKey())) { 621 updateSpeechRate((Integer) objValue); 622 } else if (KEY_DEFAULT_PITCH.equals(preference.getKey())) { 623 updateSpeechPitchValue((Integer) objValue); 624 } else if (preference == mLocalePreference) { 625 String localeString = (String) objValue; 626 updateLanguageTo( 627 (!TextUtils.isEmpty(localeString) 628 ? mEnginesHelper.parseLocaleString(localeString) 629 : null)); 630 checkDefaultLocale(); 631 return true; 632 } 633 return true; 634 } 635 updateLanguageTo(Locale locale)636 private void updateLanguageTo(Locale locale) { 637 int selectedLocaleIndex = -1; 638 String localeString = (locale != null) ? locale.toString() : ""; 639 for (int i = 0; i < mLocalePreference.getEntryValues().length; i++) { 640 if (localeString.equalsIgnoreCase(mLocalePreference.getEntryValues()[i].toString())) { 641 selectedLocaleIndex = i; 642 break; 643 } 644 } 645 646 if (selectedLocaleIndex == -1) { 647 Log.w(TAG, "updateLanguageTo called with unknown locale argument"); 648 return; 649 } 650 mLocalePreference.setSummary(mLocalePreference.getEntries()[selectedLocaleIndex]); 651 mSelectedLocaleIndex = selectedLocaleIndex; 652 653 mEnginesHelper.updateLocalePrefForEngine(mTts.getCurrentEngine(), locale); 654 655 // Null locale means "use system default" 656 mTts.setLanguage((locale != null) ? locale : Locale.getDefault()); 657 } 658 659 /** Called when mPlayButton, mResetButton is clicked. */ 660 @Override onClick(View v)661 public void onClick(View v) { 662 switch (v.getId()) { 663 case R.id.tts_play_button: 664 // Play button. 665 // Get the sample text from the TTS engine; onActivityResult will do 666 // the actual speaking 667 speakSampleText(); 668 break; 669 case R.id.tts_reset_button: 670 // Reset button. 671 int speechRateSeekbarProgress = 672 getSeekBarProgressFromValue( 673 KEY_DEFAULT_RATE, TextToSpeech.Engine.DEFAULT_RATE); 674 mDefaultRatePref.setProgress(speechRateSeekbarProgress); 675 updateSpeechRate(speechRateSeekbarProgress); 676 int pitchSeekbarProgress = 677 getSeekBarProgressFromValue( 678 KEY_DEFAULT_PITCH, TextToSpeech.Engine.DEFAULT_PITCH); 679 mDefaultPitchPref.setProgress(pitchSeekbarProgress); 680 updateSpeechPitchValue(pitchSeekbarProgress); 681 break; 682 default: 683 break; 684 } 685 } 686 updateSpeechRate(int speechRateSeekBarProgress)687 private void updateSpeechRate(int speechRateSeekBarProgress) { 688 mDefaultRate = getValueFromSeekBarProgress(KEY_DEFAULT_RATE, speechRateSeekBarProgress); 689 try { 690 android.provider.Settings.Secure.putInt( 691 getContentResolver(), TTS_DEFAULT_RATE, mDefaultRate); 692 if (mTts != null) { 693 mTts.setSpeechRate(mDefaultRate / 100.0f); 694 } 695 if (DBG) Log.d(TAG, "TTS default rate changed, now " + mDefaultRate); 696 } catch (NumberFormatException e) { 697 Log.e(TAG, "could not persist default TTS rate setting", e); 698 } 699 return; 700 } 701 updateSpeechPitchValue(int speechPitchSeekBarProgress)702 private void updateSpeechPitchValue(int speechPitchSeekBarProgress) { 703 mDefaultPitch = getValueFromSeekBarProgress(KEY_DEFAULT_PITCH, speechPitchSeekBarProgress); 704 try { 705 android.provider.Settings.Secure.putInt( 706 getContentResolver(), TTS_DEFAULT_PITCH, mDefaultPitch); 707 if (mTts != null) { 708 mTts.setPitch(mDefaultPitch / 100.0f); 709 } 710 if (DBG) Log.d(TAG, "TTS default pitch changed, now" + mDefaultPitch); 711 } catch (NumberFormatException e) { 712 Log.e(TAG, "could not persist default TTS pitch setting", e); 713 } 714 return; 715 } 716 updateWidgetState(boolean enable)717 private void updateWidgetState(boolean enable) { 718 mPlayButton.setEnabled(enable); 719 mDefaultRatePref.setEnabled(enable); 720 mDefaultPitchPref.setEnabled(enable); 721 } 722 displayNetworkAlert()723 private void displayNetworkAlert() { 724 AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); 725 builder.setTitle(android.R.string.dialog_alert_title) 726 .setMessage(getActivity().getString(R.string.tts_engine_network_required)) 727 .setCancelable(false) 728 .setPositiveButton(android.R.string.ok, null); 729 730 AlertDialog dialog = builder.create(); 731 dialog.show(); 732 } 733 734 /** Check whether the voice data for the engine is ok. */ checkVoiceData(String engine)735 private void checkVoiceData(String engine) { 736 Intent intent = new Intent(TextToSpeech.Engine.ACTION_CHECK_TTS_DATA); 737 intent.setPackage(engine); 738 try { 739 if (DBG) Log.d(TAG, "Updating engine: Checking voice data: " + intent.toUri(0)); 740 startActivityForResult(intent, VOICE_DATA_INTEGRITY_CHECK); 741 } catch (ActivityNotFoundException ex) { 742 Log.e(TAG, "Failed to check TTS data, no activity found for " + intent + ")"); 743 } 744 } 745 746 /** The voice data check is complete. */ onVoiceDataIntegrityCheckDone(Intent data)747 private void onVoiceDataIntegrityCheckDone(Intent data) { 748 final String engine = mTts.getCurrentEngine(); 749 750 if (engine == null) { 751 Log.e(TAG, "Voice data check complete, but no engine bound"); 752 return; 753 } 754 755 if (data == null){ 756 Log.e(TAG, "Engine failed voice data integrity check (null return)" + 757 mTts.getCurrentEngine()); 758 return; 759 } 760 761 android.provider.Settings.Secure.putString(getContentResolver(), TTS_DEFAULT_SYNTH, engine); 762 763 mAvailableStrLocals = data.getStringArrayListExtra( 764 TextToSpeech.Engine.EXTRA_AVAILABLE_VOICES); 765 if (mAvailableStrLocals == null) { 766 Log.e(TAG, "Voice data check complete, but no available voices found"); 767 // Set mAvailableStrLocals to empty list 768 mAvailableStrLocals = new ArrayList<String>(); 769 } 770 if (evaluateDefaultLocale()) { 771 getSampleText(); 772 } 773 } 774 775 @Override onGearClick(GearPreference p)776 public void onGearClick(GearPreference p) { 777 if (KEY_TTS_ENGINE_PREFERENCE.equals(p.getKey())) { 778 EngineInfo info = mEnginesHelper.getEngineInfo(mCurrentEngine); 779 final Intent settingsIntent = mEnginesHelper.getSettingsIntent(info.name); 780 startActivity(settingsIntent); 781 } 782 } 783 784 } 785