/tts/src/com/google/tts/TTSService.java
Java | 1546 lines | 1075 code | 138 blank | 333 comment | 219 complexity | d4d5af6571cf868a170c9f01027ae751 MD5 | raw file
Large files files are truncated, but you can click here to view the full file
1/* 2 * Copyright (C) 2009 Google Inc. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 * use this file except in compliance with the License. You may obtain a copy of 6 * 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, WITHOUT 12 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 * License for the specific language governing permissions and limitations under 14 * the License. 15 */ 16 17package com.google.tts; 18 19import android.app.Service; 20import android.content.Context; 21import android.content.Intent; 22import android.content.pm.ActivityInfo; 23import android.content.pm.PackageInfo; 24import android.content.pm.PackageManager; 25import android.content.pm.ResolveInfo; 26import android.content.pm.PackageManager.NameNotFoundException; 27import android.content.res.Resources; 28import android.media.AudioManager; 29import android.media.MediaPlayer; 30import android.media.MediaPlayer.OnCompletionListener; 31import android.net.Uri; 32import android.os.IBinder; 33import android.os.RemoteCallbackList; 34import android.os.RemoteException; 35import android.preference.PreferenceManager; 36import com.google.tts.ITTSCallback; 37import android.util.Log; 38import android.util.TypedValue; 39 40import java.io.File; 41import java.io.IOException; 42import java.io.InputStream; 43import java.util.ArrayList; 44import java.util.Arrays; 45import java.util.Enumeration; 46import java.util.HashMap; 47import java.util.List; 48import java.util.Locale; 49import java.util.Properties; 50import java.util.concurrent.locks.ReentrantLock; 51import java.util.concurrent.TimeUnit; 52 53import javax.xml.parsers.FactoryConfigurationError; 54 55/** 56 * @hide Synthesizes speech from text. This is implemented as a service so that 57 * other applications can call the TTS without needing to bundle the TTS 58 * in the build. 59 */ 60public class TTSService extends Service implements OnCompletionListener { 61 62 // This is for legacy purposes. The old TTS used languages of the format 63 // "xx-rYY" (xx denotes language and YY denotes region). 64 // This now needs to be mapped to Locale, which is of the format: 65 // xxx-YYY-variant (xxx denotes language, YYY denotes country, and variant 66 // is the name of the variant). 67 static final HashMap<String, String> langRegionToLocale = new HashMap<String, String>(); 68 static { 69 langRegionToLocale.put("af", "afr"); 70 langRegionToLocale.put("bs", "bos"); 71 langRegionToLocale.put("zh-rHK", "yue"); 72 langRegionToLocale.put("zh", "cmn"); 73 langRegionToLocale.put("hr", "hrv"); 74 langRegionToLocale.put("cz", "ces"); 75 langRegionToLocale.put("cs", "ces"); 76 langRegionToLocale.put("nl", "nld"); 77 langRegionToLocale.put("en", "eng"); 78 langRegionToLocale.put("en-rUS", "eng-USA"); 79 langRegionToLocale.put("en-rGB", "eng-GBR"); 80 langRegionToLocale.put("eo", "epo"); 81 langRegionToLocale.put("fi", "fin"); 82 langRegionToLocale.put("fr", "fra"); 83 langRegionToLocale.put("fr-rFR", "fra-FRA"); 84 langRegionToLocale.put("de", "deu"); 85 langRegionToLocale.put("de-rDE", "deu-DEU"); 86 langRegionToLocale.put("el", "ell"); 87 langRegionToLocale.put("hi", "hin"); 88 langRegionToLocale.put("hu", "hun"); 89 langRegionToLocale.put("is", "isl"); 90 langRegionToLocale.put("id", "ind"); 91 langRegionToLocale.put("it", "ita"); 92 langRegionToLocale.put("it-rIT", "ita-ITA"); 93 langRegionToLocale.put("ku", "kur"); 94 langRegionToLocale.put("la", "lat"); 95 langRegionToLocale.put("mk", "mkd"); 96 langRegionToLocale.put("no", "nor"); 97 langRegionToLocale.put("pl", "pol"); 98 langRegionToLocale.put("pt", "por"); 99 langRegionToLocale.put("ro", "ron"); 100 langRegionToLocale.put("ru", "rus"); 101 langRegionToLocale.put("sr", "srp"); 102 langRegionToLocale.put("sk", "slk"); 103 langRegionToLocale.put("es", "spa"); 104 langRegionToLocale.put("es-rES", "spa-ESP"); 105 langRegionToLocale.put("es-rMX", "spa-MEX"); 106 langRegionToLocale.put("sw", "swa"); 107 langRegionToLocale.put("sv", "swe"); 108 langRegionToLocale.put("ta", "tam"); 109 langRegionToLocale.put("tr", "tur"); 110 langRegionToLocale.put("vi", "vie"); 111 langRegionToLocale.put("cy", "cym"); 112 } 113 114 private static class SpeechItem { 115 public static final int TEXT = 0; 116 117 public static final int EARCON = 1; 118 119 public static final int SILENCE = 2; 120 121 public static final int TEXT_TO_FILE = 3; 122 123 public String mText = ""; 124 125 public ArrayList<String> mParams = null; 126 127 public int mType = TEXT; 128 129 public long mDuration = 0; 130 131 public String mFilename = null; 132 133 public String mCallingApp = ""; 134 135 public SpeechItem(String source, String text, ArrayList<String> params, int itemType) { 136 mText = text; 137 mParams = params; 138 mType = itemType; 139 mCallingApp = source; 140 } 141 142 public SpeechItem(String source, long silenceTime, ArrayList<String> params) { 143 mDuration = silenceTime; 144 mParams = params; 145 mType = SILENCE; 146 mCallingApp = source; 147 } 148 149 public SpeechItem(String source, String text, ArrayList<String> params, 150 int itemType, String filename) { 151 mText = text; 152 mParams = params; 153 mType = itemType; 154 mFilename = filename; 155 mCallingApp = source; 156 } 157 158 } 159 160 /** 161 * Contains the information needed to access a sound resource; the name of 162 * the package that contains the resource and the resID of the resource 163 * within that package. 164 */ 165 private static class SoundResource { 166 public String mSourcePackageName = null; 167 168 public int mResId = -1; 169 170 public String mFilename = null; 171 172 public SoundResource(String packageName, int id) { 173 mSourcePackageName = packageName; 174 mResId = id; 175 mFilename = null; 176 } 177 178 public SoundResource(String file) { 179 mSourcePackageName = null; 180 mResId = -1; 181 mFilename = file; 182 } 183 } 184 185 // If the speech queue is locked for more than 5 seconds, something has gone 186 // very wrong with processSpeechQueue. 187 private static final int SPEECHQUEUELOCK_TIMEOUT = 5000; 188 189 private static final int MAX_SPEECH_ITEM_CHAR_LENGTH = 4000; 190 191 private static final int MAX_FILENAME_LENGTH = 250; 192 193 // TODO use the TTS stream type when available 194 private static final int DEFAULT_STREAM_TYPE = AudioManager.STREAM_MUSIC; 195 196 private static final String ACTION = "android.intent.action.USE_TTS"; 197 198 private static final String CATEGORY = "android.intent.category.TTS"; 199 200 private static final String BETA_ACTION = "com.google.intent.action.START_TTS_SERVICE_BETA"; 201 202 private static final String BETA_CATEGORY = "com.google.intent.category.TTS_BETA"; 203 204 private static final String PKGNAME = "android.tts"; 205 206 // Change this to the system/lib path when in the framework 207 private static final String DEFAULT_SYNTH = "com.svox.pico"; 208 209 protected static final String SERVICE_TAG = "TtsService"; 210 211 private final RemoteCallbackList<ITtsCallbackBeta> mCallbacks = new RemoteCallbackList<ITtsCallbackBeta>(); 212 213 private HashMap<String, ITtsCallbackBeta> mCallbacksMap; 214 215 private final RemoteCallbackList<ITTSCallback> mCallbacksOld = new RemoteCallbackList<ITTSCallback>(); 216 217 private boolean mIsSpeaking; 218 219 private boolean mSynthBusy; 220 221 private ArrayList<SpeechItem> mSpeechQueue; 222 223 private HashMap<String, SoundResource> mEarcons; 224 225 private HashMap<String, SoundResource> mUtterances; 226 227 private MediaPlayer mPlayer; 228 229 private SpeechItem mCurrentSpeechItem; 230 231 private HashMap<SpeechItem, Boolean> mKillList; // Used to ensure that 232 233 // in-flight synth calls 234 // are killed when stop is used. 235 private TTSService mSelf; 236 237 // lock for the speech queue (mSpeechQueue) and the current speech item 238 // (mCurrentSpeechItem) 239 private final ReentrantLock speechQueueLock = new ReentrantLock(); 240 241 private final ReentrantLock synthesizerLock = new ReentrantLock(); 242 243 private static SynthProxyBeta sNativeSynth = null; 244 245 private String currentSpeechEngineSOFile = ""; 246 247 private boolean deprecatedKeepBlockingFlag = false; 248 249 250 @Override 251 public void onCreate() { 252 super.onCreate(); 253 Log.v(SERVICE_TAG, "TtsService.onCreate()"); 254 255 // String soLibPath = "/data/data/com.google.tts/lib/libttspico.so"; 256 // Use this path when building in the framework: 257 // setEngine("/system/lib/libttspico.so"); 258 // setEngine("/data/data/com.google.tts/lib/libespeakengine.so"); 259 // setEngine("/data/data/com.google.tts/lib/libttspico.so"); 260 // Also, switch to using the system settings in the framework. 261 currentSpeechEngineSOFile = ""; 262 String preferredEngine = PreferenceManager.getDefaultSharedPreferences(this).getString( 263 "tts_default_synth", DEFAULT_SYNTH); 264 if (setEngine(preferredEngine) != TextToSpeechBeta.SUCCESS) { 265 Log.e(SERVICE_TAG, "Unable to start up with " + preferredEngine 266 + ". Falling back to the default TTS engine."); 267 setEngine(DEFAULT_SYNTH); 268 } 269 270 mSelf = this; 271 mIsSpeaking = false; 272 mSynthBusy = false; 273 274 mEarcons = new HashMap<String, SoundResource>(); 275 mUtterances = new HashMap<String, SoundResource>(); 276 mCallbacksMap = new HashMap<String, ITtsCallbackBeta>(); 277 278 mSpeechQueue = new ArrayList<SpeechItem>(); 279 mPlayer = null; 280 mCurrentSpeechItem = null; 281 mKillList = new HashMap<SpeechItem, Boolean>(); 282 283 setDefaultSettings(); 284 285 // Standalone library only - include a set of default earcons 286 // and pre-recorded audio. 287 // These are not in the framework due to concerns about the size. 288 Resources res = getResources(); 289 InputStream fis = res.openRawResource(R.raw.soundsamples); 290 try { 291 292 Properties soundsamples = new Properties(); 293 soundsamples.load(fis); 294 Enumeration<Object> textKeys = soundsamples.keys(); 295 while (textKeys.hasMoreElements()) { 296 String text = textKeys.nextElement().toString(); 297 String name = "com.google.tts:raw/" + soundsamples.getProperty(text); 298 TypedValue value = new TypedValue(); 299 getResources().getValue(name, value, false); 300 mUtterances.put(text, new SoundResource(PKGNAME, value.resourceId)); 301 } 302 303 } catch (FactoryConfigurationError e) { 304 e.printStackTrace(); 305 } catch (IOException e) { 306 e.printStackTrace(); 307 } catch (IllegalArgumentException e) { 308 e.printStackTrace(); 309 } catch (SecurityException e) { 310 e.printStackTrace(); 311 } 312 313 // Deprecated - these should be earcons from now on! 314 // Leave this here for one more version before removing it completely. 315 mUtterances.put("[tock]", new SoundResource(PKGNAME, R.raw.tock_snd)); 316 mUtterances.put("[slnc]", new SoundResource(PKGNAME, R.raw.slnc_snd)); 317 318 mEarcons.put("[tock]", new SoundResource(PKGNAME, R.raw.tock_snd)); 319 mEarcons.put("[slnc]", new SoundResource(PKGNAME, R.raw.slnc_snd)); 320 321 Log.e(SERVICE_TAG, "onCreate completed."); 322 } 323 324 @Override 325 public void onDestroy() { 326 super.onDestroy(); 327 328 killAllUtterances(); 329 330 // Don't hog the media player 331 cleanUpPlayer(); 332 333 if (sNativeSynth != null) { 334 sNativeSynth.shutdown(); 335 } 336 sNativeSynth = null; 337 338 // Unregister all callbacks. 339 mCallbacks.kill(); 340 mCallbacksOld.kill(); 341 342 Log.v(SERVICE_TAG, "onDestroy() completed"); 343 } 344 345 private int setEngine(String enginePackageName) { 346 if (isDefaultEnforced()){ 347 enginePackageName = getDefaultEngine(); 348 } 349 350 // This is a hack to prevent the user from trying to run the Pico 351 // engine if they are on Cupcake since the Pico auto-install only works 352 // on 353 // Donut or higher and no equivalent has not been backported. 354 int sdkInt = 4; 355 try { 356 sdkInt = Integer.parseInt(android.os.Build.VERSION.SDK); 357 } catch (NumberFormatException e) { 358 Log.e(SERVICE_TAG, "Unable to parse SDK version: " + android.os.Build.VERSION.SDK); 359 } 360 if ((sdkInt < 4) && enginePackageName.equals("com.svox.pico")) { 361 enginePackageName = "com.google.tts"; 362 } 363 364 String soFilename = ""; 365 // The SVOX TTS is an exception to how the TTS packaging scheme works 366 // because 367 // it is part of the system and not a 3rd party add-on; thus its binary 368 // is 369 // actually located under /system/lib/ 370 if (enginePackageName.equals("com.svox.pico")) { 371 // This is the path to use when this is integrated with the 372 // framework 373 // soFilename = "/system/lib/libttspico.so"; 374 if (sdkInt < 5) { 375 soFilename = "/data/data/com.google.tts/lib/libttspico_4.so"; 376 } else { 377 soFilename = "/data/data/com.google.tts/lib/libttspico.so"; 378 } 379 } else { 380 // Find the package 381 // This is the correct way to do this; but it won't work in Cupcake. 382 // :( 383 // Intent intent = new 384 // Intent("android.intent.action.START_TTS_ENGINE"); 385 // intent.setPackage(enginePackageName); 386 // ResolveInfo[] enginesArray = new ResolveInfo[0]; 387 // PackageManager pm = getPackageManager(); 388 // List <ResolveInfo> resolveInfos = 389 // pm.queryIntentActivities(intent, 0); 390 // if ((resolveInfos == null) || resolveInfos.isEmpty()) { 391 // Log.e(SERVICE_TAG, "Invalid TTS Engine Package: " + 392 // enginePackageName); 393 // return TextToSpeechBeta.ERROR; 394 // } 395 // enginesArray = resolveInfos.toArray(enginesArray); 396 // // Generate the TTS .so filename from the package 397 // ActivityInfo aInfo = enginesArray[0].activityInfo; 398 // soFilename = aInfo.name.replace(aInfo.packageName + ".", "") + 399 // ".so"; 400 // soFilename = soFilename.toLowerCase(); 401 // soFilename = "/data/data/" + aInfo.packageName + "/lib/libtts" + 402 // soFilename; 403 404 /* Start of hacky way of doing this */ 405 // Using a loop since we can't set the package name for the intent 406 // in 407 // Cupcake 408 Intent intent = new Intent("android.intent.action.START_TTS_ENGINE"); 409 ResolveInfo[] enginesArray = new ResolveInfo[0]; 410 PackageManager pm = getPackageManager(); 411 List<ResolveInfo> resolveInfos = pm.queryIntentActivities(intent, 0); 412 enginesArray = resolveInfos.toArray(enginesArray); 413 ActivityInfo aInfo = null; 414 for (int i = 0; i < enginesArray.length; i++) { 415 if (enginesArray[i].activityInfo.packageName.equals(enginePackageName)) { 416 aInfo = enginesArray[i].activityInfo; 417 break; 418 } 419 } 420 if (aInfo == null) { 421 Log.e(SERVICE_TAG, "Invalid TTS Engine Package: " + enginePackageName); 422 return TextToSpeechBeta.ERROR; 423 } 424 425 // Try to get a platform SDK specific binary 426 if (sdkInt < 5) { 427 sdkInt = 4; 428 } 429 soFilename = aInfo.name.replace(aInfo.packageName + ".", "") + "_" + sdkInt + ".so"; 430 soFilename = soFilename.toLowerCase(); 431 soFilename = "/data/data/" + aInfo.packageName + "/lib/libtts" + soFilename; 432 File f = new File(soFilename); 433 // If no such binary is available, default to a generic binary 434 if (!f.exists()) { 435 soFilename = aInfo.name.replace(aInfo.packageName + ".", "") + ".so"; 436 soFilename = soFilename.toLowerCase(); 437 soFilename = "/data/data/" + aInfo.packageName + "/lib/libtts" + soFilename; 438 } 439 440 /* End of hacky way of doing this */ 441 } 442 443 if (currentSpeechEngineSOFile.equals(soFilename)) { 444 return TextToSpeechBeta.SUCCESS; 445 } 446 447 File f = new File(soFilename); 448 if (!f.exists()) { 449 Log.e(SERVICE_TAG, "Invalid TTS Binary: " + soFilename); 450 return TextToSpeechBeta.ERROR; 451 } 452 if (sNativeSynth != null) { 453 // Should really be a stopSync here, but that is not available in 454 // Donut... 455 // sNativeSynth.stopSync(); 456 sNativeSynth.stop(); 457 sNativeSynth.shutdown(); 458 sNativeSynth = null; 459 } 460 sNativeSynth = new SynthProxyBeta(soFilename); 461 currentSpeechEngineSOFile = soFilename; 462 return TextToSpeechBeta.SUCCESS; 463 } 464 465 private void setDefaultSettings() { 466 setLanguage("", getDefaultLanguage(), getDefaultCountry(), getDefaultLocVariant()); 467 468 // speech rate 469 setSpeechRate("", getDefaultRate()); 470 } 471 472 private boolean isDefaultEnforced() { 473 return (PreferenceManager.getDefaultSharedPreferences(this).getInt("toggle_use_default_tts_settings", 474 0) == 1); 475 // In the framework, use the Secure settings instead by doing: 476 // 477 // return (android.provider.Settings.Secure.getInt(mResolver, 478 // android.provider.Settings.Secure.TTS_USE_DEFAULTS, 479 // TextToSpeechBeta.Engine.USE_DEFAULTS) 480 // == 1 ); 481 } 482 483 private String getDefaultEngine() { 484 // In the framework, use the Secure settings instead by doing: 485 // String defaultEngine = android.provider.Settings.Secure.getString(mResolver, 486 // android.provider.Settings.Secure.TTS_DEFAULT_SYNTH); 487 String defaultEngine = PreferenceManager.getDefaultSharedPreferences(this).getString("tts_default_synth", DEFAULT_SYNTH); 488 if (defaultEngine == null) { 489 return TextToSpeechBeta.Engine.DEFAULT_SYNTH; 490 } else { 491 return defaultEngine; 492 } 493 } 494 495 private int getDefaultRate() { 496 return Integer.parseInt(PreferenceManager.getDefaultSharedPreferences(this).getString( 497 "rate_pref", "100")); 498 // In the framework, use the Secure settings instead by doing: 499 // 500 // return android.provider.Settings.Secure.getInt(mResolver, 501 // android.provider.Settings.Secure.TTS_DEFAULT_RATE, 502 // TextToSpeechBeta.Engine.DEFAULT_RATE); 503 } 504 505 private int getDefaultPitch() { 506 // Pitch is not user settable; the default pitch is always 100. 507 return 100; 508 } 509 510 private String getDefaultLanguage() { 511 return PreferenceManager.getDefaultSharedPreferences(this).getString( 512 "tts_default_lang", Locale.getDefault().getISO3Language()); 513 514 // In the framework, use the Secure settings instead by doing: 515 // 516 // String defaultLang = 517 // android.provider.Settings.Secure.getString(mResolver, 518 // android.provider.Settings.Secure.TTS_DEFAULT_LANG); 519 } 520 521 private String getDefaultCountry() { 522 return PreferenceManager.getDefaultSharedPreferences(this).getString( 523 "tts_default_country", Locale.getDefault().getISO3Country()); 524 525 // In the framework, use the Secure settings instead by doing: 526 // 527 // String defaultCountry = 528 // android.provider.Settings.Secure.getString(mResolver, 529 // android.provider.Settings.Secure.TTS_DEFAULT_COUNTRY); 530 } 531 532 private String getDefaultLocVariant() { 533 return PreferenceManager.getDefaultSharedPreferences(this).getString( 534 "tts_default_variant", Locale.getDefault().getVariant()); 535 // In the framework, use the Secure settings instead by doing: 536 // 537 // String defaultVar = 538 // android.provider.Settings.Secure.getString(mResolver, 539 // android.provider.Settings.Secure.TTS_DEFAULT_VARIANT); 540 } 541 542 private int setSpeechRate(String callingApp, int rate) { 543 int res = TextToSpeechBeta.ERROR; 544 try { 545 if (isDefaultEnforced()) { 546 res = sNativeSynth.setSpeechRate(getDefaultRate()); 547 } else { 548 res = sNativeSynth.setSpeechRate(rate); 549 } 550 } catch (NullPointerException e) { 551 // synth will become null during onDestroy() 552 res = TextToSpeechBeta.ERROR; 553 } 554 return res; 555 } 556 557 private int setPitch(String callingApp, int pitch) { 558 int res = TextToSpeechBeta.ERROR; 559 try { 560 res = sNativeSynth.setPitch(pitch); 561 } catch (NullPointerException e) { 562 // synth will become null during onDestroy() 563 res = TextToSpeechBeta.ERROR; 564 } 565 return res; 566 } 567 568 private int isLanguageAvailable(String lang, String country, String variant) { 569 int res = TextToSpeechBeta.LANG_NOT_SUPPORTED; 570 try { 571 res = sNativeSynth.isLanguageAvailable(lang, country, variant); 572 } catch (NullPointerException e) { 573 // synth will become null during onDestroy() 574 res = TextToSpeechBeta.LANG_NOT_SUPPORTED; 575 } 576 return res; 577 } 578 579 private String[] getLanguage() { 580 try { 581 return sNativeSynth.getLanguage(); 582 } catch (Exception e) { 583 return null; 584 } 585 } 586 587 private int setLanguage(String callingApp, String lang, String country, String variant) { 588 Log 589 .v(SERVICE_TAG, "TtsService.setLanguage(" + lang + ", " + country + ", " + variant 590 + ")"); 591 int res = TextToSpeechBeta.ERROR; 592 try { 593 if (isDefaultEnforced()) { 594 res = sNativeSynth.setLanguage(getDefaultLanguage(), getDefaultCountry(), 595 getDefaultLocVariant()); 596 } else { 597 res = sNativeSynth.setLanguage(lang, country, variant); 598 } 599 } catch (NullPointerException e) { 600 // synth will become null during onDestroy() 601 res = TextToSpeechBeta.ERROR; 602 } 603 return res; 604 } 605 606 /** 607 * Adds a sound resource to the TTS. 608 * 609 * @param text The text that should be associated with the sound resource 610 * @param packageName The name of the package which has the sound resource 611 * @param resId The resource ID of the sound within its package 612 */ 613 private void addSpeech(String callingApp, String text, String packageName, int resId) { 614 mUtterances.put(text, new SoundResource(packageName, resId)); 615 } 616 617 /** 618 * Adds a sound resource to the TTS. 619 * 620 * @param text The text that should be associated with the sound resource 621 * @param filename The filename of the sound resource. This must be a 622 * complete path like: (/sdcard/mysounds/mysoundbite.mp3). 623 */ 624 private void addSpeech(String callingApp, String text, String filename) { 625 mUtterances.put(text, new SoundResource(filename)); 626 } 627 628 /** 629 * Adds a sound resource to the TTS as an earcon. 630 * 631 * @param earcon The text that should be associated with the sound resource 632 * @param packageName The name of the package which has the sound resource 633 * @param resId The resource ID of the sound within its package 634 */ 635 private void addEarcon(String callingApp, String earcon, String packageName, int resId) { 636 mEarcons.put(earcon, new SoundResource(packageName, resId)); 637 } 638 639 /** 640 * Adds a sound resource to the TTS as an earcon. 641 * 642 * @param earcon The text that should be associated with the sound resource 643 * @param filename The filename of the sound resource. This must be a 644 * complete path like: (/sdcard/mysounds/mysoundbite.mp3). 645 */ 646 private void addEarcon(String callingApp, String earcon, String filename) { 647 mEarcons.put(earcon, new SoundResource(filename)); 648 } 649 650 /** 651 * Speaks the given text using the specified queueing mode and parameters. 652 * 653 * @param text The text that should be spoken 654 * @param queueMode TextToSpeech.TTS_QUEUE_FLUSH for no queue (interrupts 655 * all previous utterances), TextToSpeech.TTS_QUEUE_ADD for 656 * queued 657 * @param params An ArrayList of parameters. This is not implemented for all 658 * engines. 659 */ 660 private int speak(String callingApp, String text, int queueMode, ArrayList<String> params) { 661 Log.v(SERVICE_TAG, "TTS service received " + text); 662 if (queueMode == TextToSpeechBeta.QUEUE_FLUSH) { 663 stop(callingApp); 664 } else if (queueMode == 2) { 665 stopAll(callingApp); 666 } 667 mSpeechQueue.add(new SpeechItem(callingApp, text, params, SpeechItem.TEXT)); 668 if (!mIsSpeaking) { 669 processSpeechQueue(); 670 } 671 return TextToSpeechBeta.SUCCESS; 672 } 673 674 /** 675 * Plays the earcon using the specified queueing mode and parameters. 676 * 677 * @param earcon The earcon that should be played 678 * @param queueMode TextToSpeech.TTS_QUEUE_FLUSH for no queue (interrupts 679 * all previous utterances), TextToSpeech.TTS_QUEUE_ADD for 680 * queued 681 * @param params An ArrayList of parameters. This is not implemented for all 682 * engines. 683 */ 684 private int playEarcon(String callingApp, String earcon, int queueMode, ArrayList<String> params) { 685 if (queueMode == TextToSpeechBeta.QUEUE_FLUSH) { 686 stop(callingApp); 687 } else if (queueMode == 2) { 688 stopAll(callingApp); 689 } 690 mSpeechQueue.add(new SpeechItem(callingApp, earcon, params, SpeechItem.EARCON)); 691 if (!mIsSpeaking) { 692 processSpeechQueue(); 693 } 694 return TextToSpeechBeta.SUCCESS; 695 } 696 697 /** 698 * Stops all speech output and removes any utterances still in the queue for 699 * the calling app. 700 */ 701 private int stop(String callingApp) { 702 int result = TextToSpeechBeta.ERROR; 703 boolean speechQueueAvailable = false; 704 try{ 705 speechQueueAvailable = 706 speechQueueLock.tryLock(SPEECHQUEUELOCK_TIMEOUT, TimeUnit.MILLISECONDS); 707 if (speechQueueAvailable) { 708 Log.i(SERVICE_TAG, "Stopping"); 709 for (int i = mSpeechQueue.size() - 1; i > -1; i--){ 710 if (mSpeechQueue.get(i).mCallingApp.equals(callingApp)){ 711 mSpeechQueue.remove(i); 712 } 713 } 714 if ((mCurrentSpeechItem != null) && 715 mCurrentSpeechItem.mCallingApp.equals(callingApp)) { 716 try { 717 result = sNativeSynth.stop(); 718 } catch (NullPointerException e1) { 719 // synth will become null during onDestroy() 720 result = TextToSpeechBeta.ERROR; 721 } 722 mKillList.put(mCurrentSpeechItem, true); 723 if (mPlayer != null) { 724 try { 725 mPlayer.stop(); 726 } catch (IllegalStateException e) { 727 // Do nothing, the player is already stopped. 728 } 729 } 730 mIsSpeaking = false; 731 mCurrentSpeechItem = null; 732 } else { 733 result = TextToSpeechBeta.SUCCESS; 734 } 735 Log.i(SERVICE_TAG, "Stopped"); 736 } else { 737 Log.e(SERVICE_TAG, "TTS stop(): queue locked longer than expected"); 738 result = TextToSpeechBeta.ERROR; 739 } 740 } catch (InterruptedException e) { 741 Log.e(SERVICE_TAG, "TTS stop: tryLock interrupted"); 742 e.printStackTrace(); 743 } finally { 744 // This check is needed because finally will always run; even if the 745 // method returns somewhere in the try block. 746 if (speechQueueAvailable) { 747 speechQueueLock.unlock(); 748 } 749 return result; 750 } 751 } 752 753 /** 754 * Stops all speech output, both rendered to a file and directly spoken, and 755 * removes any utterances still in the queue globally. Files that were being 756 * written are deleted. 757 */ 758 @SuppressWarnings("finally") 759 private int killAllUtterances() { 760 int result = TextToSpeechBeta.ERROR; 761 boolean speechQueueAvailable = false; 762 763 try { 764 speechQueueAvailable = speechQueueLock.tryLock(SPEECHQUEUELOCK_TIMEOUT, 765 TimeUnit.MILLISECONDS); 766 if (speechQueueAvailable) { 767 // remove every single entry in the speech queue 768 mSpeechQueue.clear(); 769 770 // clear the current speech item 771 if (mCurrentSpeechItem != null) { 772 // Should be a stopSync - only using a stop for Donut 773 // compatibility 774 // result = sNativeSynth.stopSync(); 775 result = sNativeSynth.stop(); 776 mKillList.put(mCurrentSpeechItem, true); 777 mIsSpeaking = false; 778 779 // was the engine writing to a file? 780 if (mCurrentSpeechItem.mType == SpeechItem.TEXT_TO_FILE) { 781 // delete the file that was being written 782 if (mCurrentSpeechItem.mFilename != null) { 783 File tempFile = new File(mCurrentSpeechItem.mFilename); 784 Log.v(SERVICE_TAG, "Leaving behind " + mCurrentSpeechItem.mFilename); 785 if (tempFile.exists()) { 786 Log.v(SERVICE_TAG, "About to delete " 787 + mCurrentSpeechItem.mFilename); 788 if (tempFile.delete()) { 789 Log.v(SERVICE_TAG, "file successfully deleted"); 790 } 791 } 792 } 793 } 794 795 mCurrentSpeechItem = null; 796 } 797 } else { 798 Log.e(SERVICE_TAG, "TTS killAllUtterances(): queue locked longer than expected"); 799 result = TextToSpeechBeta.ERROR; 800 } 801 } catch (InterruptedException e) { 802 Log.e(SERVICE_TAG, "TTS killAllUtterances(): tryLock interrupted"); 803 result = TextToSpeechBeta.ERROR; 804 } finally { 805 // This check is needed because finally will always run, even if the 806 // method returns somewhere in the try block. 807 if (speechQueueAvailable) { 808 speechQueueLock.unlock(); 809 } 810 return result; 811 } 812 } 813 814 /** 815 * Stops all speech output and removes any utterances still in the queue 816 * globally, except those intended to be synthesized to file. 817 */ 818 private int stopAll(String callingApp) { 819 int result = TextToSpeechBeta.ERROR; 820 boolean speechQueueAvailable = false; 821 try{ 822 speechQueueAvailable = 823 speechQueueLock.tryLock(SPEECHQUEUELOCK_TIMEOUT, TimeUnit.MILLISECONDS); 824 if (speechQueueAvailable) { 825 for (int i = mSpeechQueue.size() - 1; i > -1; i--){ 826 if (mSpeechQueue.get(i).mType != SpeechItem.TEXT_TO_FILE){ 827 mSpeechQueue.remove(i); 828 } 829 } 830 if ((mCurrentSpeechItem != null) && 831 ((mCurrentSpeechItem.mType != SpeechItem.TEXT_TO_FILE) || 832 mCurrentSpeechItem.mCallingApp.equals(callingApp))) { 833 try { 834 result = sNativeSynth.stop(); 835 } catch (NullPointerException e1) { 836 // synth will become null during onDestroy() 837 result = TextToSpeechBeta.ERROR; 838 } 839 mKillList.put(mCurrentSpeechItem, true); 840 if (mPlayer != null) { 841 try { 842 mPlayer.stop(); 843 } catch (IllegalStateException e) { 844 // Do nothing, the player is already stopped. 845 } 846 } 847 mIsSpeaking = false; 848 mCurrentSpeechItem = null; 849 } else { 850 result = TextToSpeechBeta.SUCCESS; 851 } 852 Log.i(SERVICE_TAG, "Stopped all"); 853 } else { 854 Log.e(SERVICE_TAG, "TTS stopAll(): queue locked longer than expected"); 855 result = TextToSpeechBeta.ERROR; 856 } 857 } catch (InterruptedException e) { 858 Log.e(SERVICE_TAG, "TTS stopAll: tryLock interrupted"); 859 e.printStackTrace(); 860 } finally { 861 // This check is needed because finally will always run; even if the 862 // method returns somewhere in the try block. 863 if (speechQueueAvailable) { 864 speechQueueLock.unlock(); 865 } 866 return result; 867 } 868 } 869 870 public void onCompletion(MediaPlayer arg0) { 871 // mCurrentSpeechItem may become null if it is stopped at the same 872 // time it completes. 873 SpeechItem currentSpeechItemCopy = mCurrentSpeechItem; 874 if (currentSpeechItemCopy != null) { 875 String callingApp = currentSpeechItemCopy.mCallingApp; 876 ArrayList<String> params = currentSpeechItemCopy.mParams; 877 String utteranceId = ""; 878 if (params != null) { 879 for (int i = 0; i < params.size() - 1; i = i + 2) { 880 String param = params.get(i); 881 if (param.equals(TextToSpeechBeta.Engine.KEY_PARAM_UTTERANCE_ID)) { 882 utteranceId = params.get(i + 1); 883 } 884 } 885 } 886 if (utteranceId.length() > 0) { 887 dispatchUtteranceCompletedCallback(utteranceId, callingApp); 888 } 889 } 890 processSpeechQueue(); 891 } 892 893 private int playSilence(String callingApp, long duration, int queueMode, 894 ArrayList<String> params) { 895 if (queueMode == TextToSpeechBeta.QUEUE_FLUSH) { 896 stop(callingApp); 897 } 898 mSpeechQueue.add(new SpeechItem(callingApp, duration, params)); 899 if (!mIsSpeaking) { 900 processSpeechQueue(); 901 } 902 return TextToSpeechBeta.SUCCESS; 903 } 904 905 private void silence(final SpeechItem speechItem) { 906 class SilenceThread implements Runnable { 907 public void run() { 908 String utteranceId = ""; 909 if (speechItem.mParams != null){ 910 for (int i = 0; i < speechItem.mParams.size() - 1; i = i + 2){ 911 String param = speechItem.mParams.get(i); 912 if (param.equals(TextToSpeechBeta.Engine.KEY_PARAM_UTTERANCE_ID)){ 913 utteranceId = speechItem.mParams.get(i+1); 914 } 915 } 916 } 917 try { 918 Thread.sleep(speechItem.mDuration); 919 } catch (InterruptedException e) { 920 e.printStackTrace(); 921 } finally { 922 if (utteranceId.length() > 0){ 923 dispatchUtteranceCompletedCallback(utteranceId, speechItem.mCallingApp); 924 } 925 processSpeechQueue(); 926 } 927 } 928 } 929 Thread slnc = (new Thread(new SilenceThread())); 930 slnc.setPriority(Thread.MIN_PRIORITY); 931 slnc.start(); 932 } 933 934 boolean synthThreadBusy = false; 935 936 private void speakInternalOnly(final SpeechItem speechItem) { 937 Log.e(SERVICE_TAG, "Creating synth thread for: " + speechItem.mText); 938 class SynthThread implements Runnable { 939 public void run() { 940 boolean synthAvailable = false; 941 String utteranceId = ""; 942 try { 943 synthAvailable = synthesizerLock.tryLock(); 944 if (!synthAvailable) { 945 mSynthBusy = true; 946 Thread.sleep(100); 947 Thread synth = (new Thread(new SynthThread())); 948 synth.start(); 949 mSynthBusy = false; 950 return; 951 } 952 int streamType = DEFAULT_STREAM_TYPE; 953 String language = ""; 954 String country = ""; 955 String variant = ""; 956 String speechRate = ""; 957 String engine = ""; 958 String pitch = ""; 959 if (speechItem.mParams != null){ 960 for (int i = 0; i < speechItem.mParams.size() - 1; i = i + 2){ 961 String param = speechItem.mParams.get(i); 962 if (param != null) { 963 if (param.equals(TextToSpeechBeta.Engine.KEY_PARAM_RATE)) { 964 speechRate = speechItem.mParams.get(i + 1); 965 } else if (param.equals(TextToSpeechBeta.Engine.KEY_PARAM_LANGUAGE)) { 966 language = speechItem.mParams.get(i + 1); 967 } else if (param.equals(TextToSpeechBeta.Engine.KEY_PARAM_COUNTRY)) { 968 country = speechItem.mParams.get(i + 1); 969 } else if (param.equals(TextToSpeechBeta.Engine.KEY_PARAM_VARIANT)) { 970 variant = speechItem.mParams.get(i + 1); 971 } else if (param 972 .equals(TextToSpeechBeta.Engine.KEY_PARAM_UTTERANCE_ID)) { 973 utteranceId = speechItem.mParams.get(i + 1); 974 } else if (param.equals(TextToSpeechBeta.Engine.KEY_PARAM_STREAM)) { 975 try { 976 streamType = Integer 977 .parseInt(speechItem.mParams.get(i + 1)); 978 } catch (NumberFormatException e) { 979 streamType = DEFAULT_STREAM_TYPE; 980 } 981 } else if (param.equals(TextToSpeechBeta.Engine.KEY_PARAM_ENGINE)) { 982 engine = speechItem.mParams.get(i + 1); 983 } else if (param.equals(TextToSpeechBeta.Engine.KEY_PARAM_PITCH)) { 984 pitch = speechItem.mParams.get(i + 1); 985 } 986 } 987 } 988 } 989 // Only do the synthesis if it has not been killed by a subsequent utterance. 990 if (mKillList.get(speechItem) == null) { 991 if (engine.length() > 0) { 992 setEngine(engine); 993 } else { 994 setEngine(getDefaultEngine()); 995 } 996 if (language.length() > 0){ 997 setLanguage("", language, country, variant); 998 } else { 999 setLanguage("", getDefaultLanguage(), getDefaultCountry(), 1000 getDefaultLocVariant()); 1001 } 1002 if (speechRate.length() > 0){ 1003 setSpeechRate("", Integer.parseInt(speechRate)); 1004 } else { 1005 setSpeechRate("", getDefaultRate()); 1006 } 1007 if (pitch.length() > 0){ 1008 setPitch("", Integer.parseInt(pitch)); 1009 } else { 1010 setPitch("", getDefaultPitch()); 1011 } 1012 try { 1013 sNativeSynth.speak(speechItem.mText, streamType); 1014 } catch (NullPointerException e) { 1015 // synth will become null during onDestroy() 1016 Log.v(SERVICE_TAG, " null synth, can't speak"); 1017 } 1018 } 1019 } catch (InterruptedException e) { 1020 Log.e(SERVICE_TAG, "TTS speakInternalOnly(): tryLock interrupted"); 1021 e.printStackTrace(); 1022 } finally { 1023 // This check is needed because finally will always run; 1024 // even if the 1025 // method returns somewhere in the try block. 1026 if (utteranceId.length() > 0){ 1027 dispatchUtteranceCompletedCallback(utteranceId, speechItem.mCallingApp); 1028 } 1029 if (synthAvailable) { 1030 synthesizerLock.unlock(); 1031 processSpeechQueue(); 1032 } 1033 } 1034 } 1035 } 1036 Thread synth = (new Thread(new SynthThread())); 1037 synth.setPriority(Thread.MAX_PRIORITY); 1038 synth.start(); 1039 } 1040 1041 private void synthToFileInternalOnly(final SpeechItem speechItem) { 1042 class SynthThread implements Runnable { 1043 public void run() { 1044 boolean synthAvailable = false; 1045 String utteranceId = ""; 1046 Log.i(SERVICE_TAG, "Synthesizing to " + speechItem.mFilename); 1047 try { 1048 synthAvailable = synthesizerLock.tryLock(); 1049 if (!synthAvailable) { 1050 synchronized (this) { 1051 mSynthBusy = true; 1052 } 1053 Thread.sleep(100); 1054 Thread synth = (new Thread(new SynthThread())); 1055 // synth.setPriority(Thread.MIN_PRIORITY); 1056 synth.start(); 1057 synchronized (this) { 1058 mSynthBusy = false; 1059 } 1060 return; 1061 } 1062 String language = ""; 1063 String country = ""; 1064 String variant = ""; 1065 String speechRate = ""; 1066 String engine = ""; 1067 String pitch = ""; 1068 if (speechItem.mParams != null){ 1069 for (int i = 0; i < speechItem.mParams.size() - 1; i = i + 2){ 1070 String param = speechItem.mParams.get(i); 1071 if (param != null) { 1072 if (param.equals(TextToSpeechBeta.Engine.KEY_PARAM_RATE)) { 1073 speechRate = speechItem.mParams.get(i + 1); 1074 } else if (param.equals(TextToSpeechBeta.Engine.KEY_PARAM_LANGUAGE)) { 1075 language = speechItem.mParams.get(i + 1); 1076 } else if (param.equals(TextToSpeechBeta.Engine.KEY_PARAM_COUNTRY)) { 1077 country = speechItem.mParams.get(i + 1); 1078 } else if (param.equals(TextToSpeechBeta.Engine.KEY_PARAM_VARIANT)) { 1079 variant = speechItem.mParams.get(i + 1); 1080 } else if (param 1081 .equals(TextToSpeechBeta.Engine.KEY_PARAM_UTTERANCE_ID)) { 1082 utteranceId = speechItem.mParams.get(i + 1); 1083 } else if (param.equals(TextToSpeechBeta.Engine.KEY_PARAM_ENGINE)) { 1084 engine = speechItem.mParams.get(i + 1); 1085 } else if (param.equals(TextToSpeechBeta.Engine.KEY_PARAM_PITCH)) { 1086 pitch = speechItem.mParams.get(i + 1); 1087 } 1088 } 1089 } 1090 } 1091 // Only do the synthesis if it has not been killed by a subsequent utterance. 1092 if (mKillList.get(speechItem) == null){ 1093 if (engine.length() > 0) { 1094 setEngine(engine); 1095 } else { 1096 setEngine(getDefaultEngine()); 1097 } 1098 if (language.length() > 0){ 1099 setLanguage("", language, country, variant); 1100 } else { 1101 setLanguage("", getDefaultLanguage(), getDefaultCountry(), 1102 getDefaultLocVariant()); 1103 } 1104 if (speechRate.length() > 0){ 1105 setSpeechRate("", Integer.parseInt(speechRate)); 1106 } else { 1107 setSpeechRate("", getDefaultRate()); 1108 } 1109 if (pitch.length() > 0){ 1110 setPitch("", Integer.parseInt(pitch)); 1111 } else { 1112 setPitch("", getDefaultPitch()); 1113 } 1114 try { 1115 sNativeSynth.synthesizeToFile(speechItem.mText, speechItem.mFilename); 1116 } catch (NullPointerException e) { 1117 // synth will become null during onDestroy() 1118 Log.v(SERVICE_TAG, " null synth, can't synthesize to file"); 1119 } 1120 } 1121 } catch (InterruptedException e) { 1122 Log.e(SERVICE_TAG, "TTS synthToFileInternalOnly(): tryLock interrupted"); 1123 e.printStackTrace(); 1124 } finally { 1125 // This check is needed because finally will always run; 1126 // even if the 1127 // method returns somewhere in the try block. 1128 if (utteranceId.length() > 0){ 1129 dispatchUtteranceCompletedCallback(utteranceId, speechItem.mCallingApp); 1130 } 1131 if (synthAvailable) { 1132 synthesizerLock.unlock(); 1133 processSpeechQueue(); 1134 } 1135 deprecatedKeepBlockingFlag = false; 1136 } 1137 } 1138 } 1139 Thread synth = (new Thread(new SynthThread())); 1140 synth.setPriority(Thread.MAX_PRIORITY); 1141 synth.start(); 1142 } 1143 1144 private SoundResource getSoundResource(SpeechItem speechItem) { 1145 SoundResource sr = null; 1146 String text = speechItem.mText; 1147 if (speechItem.mType == SpeechItem.SILENCE) { 1148 // Do nothing if this is just silence 1149 } else if (speechItem.mType == SpeechItem.EARCON) { 1150 sr = mEarcons.get(text); 1151 } else { 1152 sr = mUtterances.get(text); 1153 } 1154 return sr; 1155 } 1156 1157 private void broadcastTtsQueueProcessingCompleted() { 1158 Intent i = new Intent(TextToSpeechBeta.ACTION_TTS_QUEUE_PROCESSING_COMPLETED); 1159 sendBroadcast(i); 1160 } 1161 1162 private void dispatchUtteranceCompletedCallback(String utteranceId, String packageName) { 1163 /* Legacy support for TTS */ 1164 final int oldN = mCallbacksOld.beginBroadcast(); 1165 for (int i = 0; i < oldN; i++) { 1166 try { 1167 mCallbacksOld.getBroadcastItem(i).markReached(""); 1168 } catch (RemoteException e) { 1169 // The RemoteCallbackList will take care of removing 1170 // the dead object for us. 1171 } 1172 } 1173 try { 1174 mCallbacksOld.finishBroadcast(); 1175 } catch (IllegalStateException e) { 1176 // May…
Large files files are truncated, but you can click here to view the full file