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 package android.tts; 17 18 import android.app.Service; 19 import android.content.ContentResolver; 20 import android.content.Context; 21 import android.content.Intent; 22 import android.content.SharedPreferences; 23 import android.content.pm.ActivityInfo; 24 import android.content.pm.PackageManager; 25 import android.content.pm.PackageManager.NameNotFoundException; 26 import android.content.pm.ResolveInfo; 27 import android.database.Cursor; 28 import android.media.AudioManager; 29 import android.media.MediaPlayer; 30 import android.media.MediaPlayer.OnCompletionListener; 31 import android.net.Uri; 32 import android.os.IBinder; 33 import android.os.RemoteCallbackList; 34 import android.os.RemoteException; 35 import android.preference.PreferenceManager; 36 import android.speech.tts.ITts.Stub; 37 import android.speech.tts.ITtsCallback; 38 import android.speech.tts.TextToSpeech; 39 import android.util.Log; 40 41 import java.io.File; 42 import java.io.IOException; 43 import java.util.ArrayList; 44 import java.util.Arrays; 45 import java.util.HashMap; 46 import java.util.List; 47 import java.util.Locale; 48 import java.util.concurrent.locks.ReentrantLock; 49 import java.util.concurrent.TimeUnit; 50 51 52 /** 53 * @hide Synthesizes speech from text. This is implemented as a service so that 54 * other applications can call the TTS without needing to bundle the TTS 55 * in the build. 56 * 57 */ 58 public class TtsService extends Service implements OnCompletionListener { 59 60 private static class SpeechItem { 61 public static final int TEXT = 0; 62 public static final int EARCON = 1; 63 public static final int SILENCE = 2; 64 public static final int TEXT_TO_FILE = 3; 65 public String mText = ""; 66 public ArrayList<String> mParams = null; 67 public int mType = TEXT; 68 public long mDuration = 0; 69 public String mFilename = null; 70 public String mCallingApp = ""; 71 SpeechItem(String source, String text, ArrayList<String> params, int itemType)72 public SpeechItem(String source, String text, ArrayList<String> params, int itemType) { 73 mText = text; 74 mParams = params; 75 mType = itemType; 76 mCallingApp = source; 77 } 78 SpeechItem(String source, long silenceTime, ArrayList<String> params)79 public SpeechItem(String source, long silenceTime, ArrayList<String> params) { 80 mDuration = silenceTime; 81 mParams = params; 82 mType = SILENCE; 83 mCallingApp = source; 84 } 85 SpeechItem(String source, String text, ArrayList<String> params, int itemType, String filename)86 public SpeechItem(String source, String text, ArrayList<String> params, 87 int itemType, String filename) { 88 mText = text; 89 mParams = params; 90 mType = itemType; 91 mFilename = filename; 92 mCallingApp = source; 93 } 94 95 } 96 97 /** 98 * Contains the information needed to access a sound resource; the name of 99 * the package that contains the resource and the resID of the resource 100 * within that package. 101 */ 102 private static class SoundResource { 103 public String mSourcePackageName = null; 104 public int mResId = -1; 105 public String mFilename = null; 106 SoundResource(String packageName, int id)107 public SoundResource(String packageName, int id) { 108 mSourcePackageName = packageName; 109 mResId = id; 110 mFilename = null; 111 } 112 SoundResource(String file)113 public SoundResource(String file) { 114 mSourcePackageName = null; 115 mResId = -1; 116 mFilename = file; 117 } 118 } 119 // If the speech queue is locked for more than 5 seconds, something has gone 120 // very wrong with processSpeechQueue. 121 private static final int SPEECHQUEUELOCK_TIMEOUT = 5000; 122 private static final int MAX_SPEECH_ITEM_CHAR_LENGTH = 4000; 123 private static final int MAX_FILENAME_LENGTH = 250; 124 // TODO use the TTS stream type when available 125 private static final int DEFAULT_STREAM_TYPE = AudioManager.STREAM_MUSIC; 126 // TODO use TextToSpeech.DEFAULT_SYNTH once it is unhidden 127 private static final String DEFAULT_SYNTH = "com.svox.pico"; 128 private static final String ACTION = "android.intent.action.START_TTS_SERVICE"; 129 private static final String CATEGORY = "android.intent.category.TTS"; 130 private static final String PKGNAME = "android.tts"; 131 protected static final String SERVICE_TAG = "TtsService"; 132 133 private final RemoteCallbackList<ITtsCallback> mCallbacks 134 = new RemoteCallbackList<ITtsCallback>(); 135 136 private HashMap<String, ITtsCallback> mCallbacksMap; 137 138 private Boolean mIsSpeaking; 139 private Boolean mSynthBusy; 140 private ArrayList<SpeechItem> mSpeechQueue; 141 private HashMap<String, SoundResource> mEarcons; 142 private HashMap<String, SoundResource> mUtterances; 143 private MediaPlayer mPlayer; 144 private SpeechItem mCurrentSpeechItem; 145 private HashMap<SpeechItem, Boolean> mKillList; // Used to ensure that in-flight synth calls 146 // are killed when stop is used. 147 private TtsService mSelf; 148 149 private ContentResolver mResolver; 150 151 // lock for the speech queue (mSpeechQueue) and the current speech item (mCurrentSpeechItem) 152 private final ReentrantLock speechQueueLock = new ReentrantLock(); 153 private final ReentrantLock synthesizerLock = new ReentrantLock(); 154 155 private static SynthProxy sNativeSynth = null; 156 private String currentSpeechEngineSOFile = ""; 157 158 @Override onCreate()159 public void onCreate() { 160 super.onCreate(); 161 Log.v("TtsService", "TtsService.onCreate()"); 162 163 mResolver = getContentResolver(); 164 165 currentSpeechEngineSOFile = ""; 166 setEngine(getDefaultEngine()); 167 168 mSelf = this; 169 mIsSpeaking = false; 170 mSynthBusy = false; 171 172 mEarcons = new HashMap<String, SoundResource>(); 173 mUtterances = new HashMap<String, SoundResource>(); 174 mCallbacksMap = new HashMap<String, android.speech.tts.ITtsCallback>(); 175 176 mSpeechQueue = new ArrayList<SpeechItem>(); 177 mPlayer = null; 178 mCurrentSpeechItem = null; 179 mKillList = new HashMap<SpeechItem, Boolean>(); 180 181 setDefaultSettings(); 182 } 183 184 @Override onDestroy()185 public void onDestroy() { 186 super.onDestroy(); 187 188 killAllUtterances(); 189 190 // Don't hog the media player 191 cleanUpPlayer(); 192 193 if (sNativeSynth != null) { 194 sNativeSynth.shutdown(); 195 } 196 sNativeSynth = null; 197 198 // Unregister all callbacks. 199 mCallbacks.kill(); 200 201 Log.v(SERVICE_TAG, "onDestroy() completed"); 202 } 203 204 setEngine(String enginePackageName)205 private int setEngine(String enginePackageName) { 206 String soFilename = ""; 207 if (isDefaultEnforced()) { 208 enginePackageName = getDefaultEngine(); 209 } 210 211 // Make sure that the engine has been allowed by the user 212 if (!enginePackageName.equals(DEFAULT_SYNTH)) { 213 String[] enabledEngines = android.provider.Settings.Secure.getString(mResolver, 214 android.provider.Settings.Secure.TTS_ENABLED_PLUGINS).split(" "); 215 boolean isEnabled = false; 216 for (int i=0; i<enabledEngines.length; i++) { 217 if (enabledEngines[i].equals(enginePackageName)) { 218 isEnabled = true; 219 break; 220 } 221 } 222 if (!isEnabled) { 223 // Do not use an engine that the user has not enabled; fall back 224 // to using the default synthesizer. 225 enginePackageName = DEFAULT_SYNTH; 226 } 227 } 228 229 // The SVOX TTS is an exception to how the TTS packaging scheme works 230 // because it is part of the system and not a 3rd party add-on; thus 231 // its binary is actually located under /system/lib/ 232 if (enginePackageName.equals(DEFAULT_SYNTH)) { 233 soFilename = "/system/lib/libttspico.so"; 234 } else { 235 // Find the package 236 Intent intent = new Intent("android.intent.action.START_TTS_ENGINE"); 237 intent.setPackage(enginePackageName); 238 ResolveInfo[] enginesArray = new ResolveInfo[0]; 239 PackageManager pm = getPackageManager(); 240 List <ResolveInfo> resolveInfos = pm.queryIntentActivities(intent, 0); 241 if ((resolveInfos == null) || resolveInfos.isEmpty()) { 242 Log.e(SERVICE_TAG, "Invalid TTS Engine Package: " + enginePackageName); 243 return TextToSpeech.ERROR; 244 } 245 enginesArray = resolveInfos.toArray(enginesArray); 246 // Generate the TTS .so filename from the package 247 ActivityInfo aInfo = enginesArray[0].activityInfo; 248 soFilename = aInfo.name.replace(aInfo.packageName + ".", "") + ".so"; 249 soFilename = soFilename.toLowerCase(); 250 soFilename = "/data/data/" + aInfo.packageName + "/lib/libtts" + soFilename; 251 } 252 253 if (currentSpeechEngineSOFile.equals(soFilename)) { 254 return TextToSpeech.SUCCESS; 255 } 256 257 File f = new File(soFilename); 258 if (!f.exists()) { 259 Log.e(SERVICE_TAG, "Invalid TTS Binary: " + soFilename); 260 return TextToSpeech.ERROR; 261 } 262 263 if (sNativeSynth != null) { 264 sNativeSynth.stopSync(); 265 sNativeSynth.shutdown(); 266 sNativeSynth = null; 267 } 268 269 // Load the engineConfig from the plugin if it has any special configuration 270 // to be loaded. By convention, if an engine wants the TTS framework to pass 271 // in any configuration, it must put it into its content provider which has the URI: 272 // content://<packageName>.providers.SettingsProvider 273 // That content provider must provide a Cursor which returns the String that 274 // is to be passed back to the native .so file for the plugin when getString(0) is 275 // called on it. 276 // Note that the TTS framework does not care what this String data is: it is something 277 // that comes from the engine plugin and is consumed only by the engine plugin itself. 278 String engineConfig = ""; 279 Cursor c = getContentResolver().query(Uri.parse("content://" + enginePackageName 280 + ".providers.SettingsProvider"), null, null, null, null); 281 if (c != null){ 282 c.moveToFirst(); 283 engineConfig = c.getString(0); 284 c.close(); 285 } 286 sNativeSynth = new SynthProxy(soFilename, engineConfig); 287 currentSpeechEngineSOFile = soFilename; 288 return TextToSpeech.SUCCESS; 289 } 290 291 292 setDefaultSettings()293 private void setDefaultSettings() { 294 setLanguage("", this.getDefaultLanguage(), getDefaultCountry(), getDefaultLocVariant()); 295 296 // speech rate 297 setSpeechRate("", getDefaultRate()); 298 } 299 300 isDefaultEnforced()301 private boolean isDefaultEnforced() { 302 return (android.provider.Settings.Secure.getInt(mResolver, 303 android.provider.Settings.Secure.TTS_USE_DEFAULTS, 304 TextToSpeech.Engine.USE_DEFAULTS) 305 == 1 ); 306 } 307 getDefaultEngine()308 private String getDefaultEngine() { 309 String defaultEngine = android.provider.Settings.Secure.getString(mResolver, 310 android.provider.Settings.Secure.TTS_DEFAULT_SYNTH); 311 if (defaultEngine == null) { 312 return TextToSpeech.Engine.DEFAULT_SYNTH; 313 } else { 314 return defaultEngine; 315 } 316 } 317 getDefaultRate()318 private int getDefaultRate() { 319 return android.provider.Settings.Secure.getInt(mResolver, 320 android.provider.Settings.Secure.TTS_DEFAULT_RATE, 321 TextToSpeech.Engine.DEFAULT_RATE); 322 } 323 getDefaultPitch()324 private int getDefaultPitch() { 325 // Pitch is not user settable; the default pitch is always 100. 326 return 100; 327 } 328 getDefaultLanguage()329 private String getDefaultLanguage() { 330 String defaultLang = android.provider.Settings.Secure.getString(mResolver, 331 android.provider.Settings.Secure.TTS_DEFAULT_LANG); 332 if (defaultLang == null) { 333 // no setting found, use the current Locale to determine the default language 334 return Locale.getDefault().getISO3Language(); 335 } else { 336 return defaultLang; 337 } 338 } 339 340 getDefaultCountry()341 private String getDefaultCountry() { 342 String defaultCountry = android.provider.Settings.Secure.getString(mResolver, 343 android.provider.Settings.Secure.TTS_DEFAULT_COUNTRY); 344 if (defaultCountry == null) { 345 // no setting found, use the current Locale to determine the default country 346 return Locale.getDefault().getISO3Country(); 347 } else { 348 return defaultCountry; 349 } 350 } 351 352 getDefaultLocVariant()353 private String getDefaultLocVariant() { 354 String defaultVar = android.provider.Settings.Secure.getString(mResolver, 355 android.provider.Settings.Secure.TTS_DEFAULT_VARIANT); 356 if (defaultVar == null) { 357 // no setting found, use the current Locale to determine the default variant 358 return Locale.getDefault().getVariant(); 359 } else { 360 return defaultVar; 361 } 362 } 363 364 setSpeechRate(String callingApp, int rate)365 private int setSpeechRate(String callingApp, int rate) { 366 int res = TextToSpeech.ERROR; 367 try { 368 if (isDefaultEnforced()) { 369 res = sNativeSynth.setSpeechRate(getDefaultRate()); 370 } else { 371 res = sNativeSynth.setSpeechRate(rate); 372 } 373 } catch (NullPointerException e) { 374 // synth will become null during onDestroy() 375 res = TextToSpeech.ERROR; 376 } 377 return res; 378 } 379 380 setPitch(String callingApp, int pitch)381 private int setPitch(String callingApp, int pitch) { 382 int res = TextToSpeech.ERROR; 383 try { 384 res = sNativeSynth.setPitch(pitch); 385 } catch (NullPointerException e) { 386 // synth will become null during onDestroy() 387 res = TextToSpeech.ERROR; 388 } 389 return res; 390 } 391 392 isLanguageAvailable(String lang, String country, String variant)393 private int isLanguageAvailable(String lang, String country, String variant) { 394 int res = TextToSpeech.LANG_NOT_SUPPORTED; 395 try { 396 res = sNativeSynth.isLanguageAvailable(lang, country, variant); 397 } catch (NullPointerException e) { 398 // synth will become null during onDestroy() 399 res = TextToSpeech.LANG_NOT_SUPPORTED; 400 } 401 return res; 402 } 403 404 getLanguage()405 private String[] getLanguage() { 406 try { 407 return sNativeSynth.getLanguage(); 408 } catch (Exception e) { 409 return null; 410 } 411 } 412 413 setLanguage(String callingApp, String lang, String country, String variant)414 private int setLanguage(String callingApp, String lang, String country, String variant) { 415 Log.v(SERVICE_TAG, "TtsService.setLanguage(" + lang + ", " + country + ", " + variant + ")"); 416 int res = TextToSpeech.ERROR; 417 try { 418 if (isDefaultEnforced()) { 419 res = sNativeSynth.setLanguage(getDefaultLanguage(), getDefaultCountry(), 420 getDefaultLocVariant()); 421 } else { 422 res = sNativeSynth.setLanguage(lang, country, variant); 423 } 424 } catch (NullPointerException e) { 425 // synth will become null during onDestroy() 426 res = TextToSpeech.ERROR; 427 } 428 return res; 429 } 430 431 432 /** 433 * Adds a sound resource to the TTS. 434 * 435 * @param text 436 * The text that should be associated with the sound resource 437 * @param packageName 438 * The name of the package which has the sound resource 439 * @param resId 440 * The resource ID of the sound within its package 441 */ addSpeech(String callingApp, String text, String packageName, int resId)442 private void addSpeech(String callingApp, String text, String packageName, int resId) { 443 mUtterances.put(text, new SoundResource(packageName, resId)); 444 } 445 446 /** 447 * Adds a sound resource to the TTS. 448 * 449 * @param text 450 * The text that should be associated with the sound resource 451 * @param filename 452 * The filename of the sound resource. This must be a complete 453 * path like: (/sdcard/mysounds/mysoundbite.mp3). 454 */ addSpeech(String callingApp, String text, String filename)455 private void addSpeech(String callingApp, String text, String filename) { 456 mUtterances.put(text, new SoundResource(filename)); 457 } 458 459 /** 460 * Adds a sound resource to the TTS as an earcon. 461 * 462 * @param earcon 463 * The text that should be associated with the sound resource 464 * @param packageName 465 * The name of the package which has the sound resource 466 * @param resId 467 * The resource ID of the sound within its package 468 */ addEarcon(String callingApp, String earcon, String packageName, int resId)469 private void addEarcon(String callingApp, String earcon, String packageName, int resId) { 470 mEarcons.put(earcon, new SoundResource(packageName, resId)); 471 } 472 473 /** 474 * Adds a sound resource to the TTS as an earcon. 475 * 476 * @param earcon 477 * The text that should be associated with the sound resource 478 * @param filename 479 * The filename of the sound resource. This must be a complete 480 * path like: (/sdcard/mysounds/mysoundbite.mp3). 481 */ addEarcon(String callingApp, String earcon, String filename)482 private void addEarcon(String callingApp, String earcon, String filename) { 483 mEarcons.put(earcon, new SoundResource(filename)); 484 } 485 486 /** 487 * Speaks the given text using the specified queueing mode and parameters. 488 * 489 * @param text 490 * The text that should be spoken 491 * @param queueMode 492 * TextToSpeech.TTS_QUEUE_FLUSH for no queue (interrupts all previous utterances), 493 * TextToSpeech.TTS_QUEUE_ADD for queued 494 * @param params 495 * An ArrayList of parameters. This is not implemented for all 496 * engines. 497 */ speak(String callingApp, String text, int queueMode, ArrayList<String> params)498 private int speak(String callingApp, String text, int queueMode, ArrayList<String> params) { 499 Log.v(SERVICE_TAG, "TTS service received " + text); 500 if (queueMode == TextToSpeech.QUEUE_FLUSH) { 501 stop(callingApp); 502 } else if (queueMode == 2) { 503 stopAll(callingApp); 504 } 505 mSpeechQueue.add(new SpeechItem(callingApp, text, params, SpeechItem.TEXT)); 506 if (!mIsSpeaking) { 507 processSpeechQueue(); 508 } 509 return TextToSpeech.SUCCESS; 510 } 511 512 /** 513 * Plays the earcon using the specified queueing mode and parameters. 514 * 515 * @param earcon 516 * The earcon that should be played 517 * @param queueMode 518 * TextToSpeech.TTS_QUEUE_FLUSH for no queue (interrupts all previous utterances), 519 * TextToSpeech.TTS_QUEUE_ADD for queued 520 * @param params 521 * An ArrayList of parameters. This is not implemented for all 522 * engines. 523 */ playEarcon(String callingApp, String earcon, int queueMode, ArrayList<String> params)524 private int playEarcon(String callingApp, String earcon, int queueMode, 525 ArrayList<String> params) { 526 if (queueMode == TextToSpeech.QUEUE_FLUSH) { 527 stop(callingApp); 528 } else if (queueMode == 2) { 529 stopAll(callingApp); 530 } 531 mSpeechQueue.add(new SpeechItem(callingApp, earcon, params, SpeechItem.EARCON)); 532 if (!mIsSpeaking) { 533 processSpeechQueue(); 534 } 535 return TextToSpeech.SUCCESS; 536 } 537 538 /** 539 * Stops all speech output and removes any utterances still in the queue for the calling app. 540 */ stop(String callingApp)541 private int stop(String callingApp) { 542 int result = TextToSpeech.ERROR; 543 boolean speechQueueAvailable = false; 544 try{ 545 speechQueueAvailable = 546 speechQueueLock.tryLock(SPEECHQUEUELOCK_TIMEOUT, TimeUnit.MILLISECONDS); 547 if (speechQueueAvailable) { 548 Log.i(SERVICE_TAG, "Stopping"); 549 for (int i = mSpeechQueue.size() - 1; i > -1; i--){ 550 if (mSpeechQueue.get(i).mCallingApp.equals(callingApp)){ 551 mSpeechQueue.remove(i); 552 } 553 } 554 if ((mCurrentSpeechItem != null) && 555 mCurrentSpeechItem.mCallingApp.equals(callingApp)) { 556 try { 557 result = sNativeSynth.stop(); 558 } catch (NullPointerException e1) { 559 // synth will become null during onDestroy() 560 result = TextToSpeech.ERROR; 561 } 562 mKillList.put(mCurrentSpeechItem, true); 563 if (mPlayer != null) { 564 try { 565 mPlayer.stop(); 566 } catch (IllegalStateException e) { 567 // Do nothing, the player is already stopped. 568 } 569 } 570 mIsSpeaking = false; 571 mCurrentSpeechItem = null; 572 } else { 573 result = TextToSpeech.SUCCESS; 574 } 575 Log.i(SERVICE_TAG, "Stopped"); 576 } else { 577 Log.e(SERVICE_TAG, "TTS stop(): queue locked longer than expected"); 578 result = TextToSpeech.ERROR; 579 } 580 } catch (InterruptedException e) { 581 Log.e(SERVICE_TAG, "TTS stop: tryLock interrupted"); 582 e.printStackTrace(); 583 } finally { 584 // This check is needed because finally will always run; even if the 585 // method returns somewhere in the try block. 586 if (speechQueueAvailable) { 587 speechQueueLock.unlock(); 588 } 589 return result; 590 } 591 } 592 593 594 /** 595 * Stops all speech output, both rendered to a file and directly spoken, and removes any 596 * utterances still in the queue globally. Files that were being written are deleted. 597 */ 598 @SuppressWarnings("finally") killAllUtterances()599 private int killAllUtterances() { 600 int result = TextToSpeech.ERROR; 601 boolean speechQueueAvailable = false; 602 603 try { 604 speechQueueAvailable = speechQueueLock.tryLock(SPEECHQUEUELOCK_TIMEOUT, 605 TimeUnit.MILLISECONDS); 606 if (speechQueueAvailable) { 607 // remove every single entry in the speech queue 608 mSpeechQueue.clear(); 609 610 // clear the current speech item 611 if (mCurrentSpeechItem != null) { 612 result = sNativeSynth.stopSync(); 613 mKillList.put(mCurrentSpeechItem, true); 614 mIsSpeaking = false; 615 616 // was the engine writing to a file? 617 if (mCurrentSpeechItem.mType == SpeechItem.TEXT_TO_FILE) { 618 // delete the file that was being written 619 if (mCurrentSpeechItem.mFilename != null) { 620 File tempFile = new File(mCurrentSpeechItem.mFilename); 621 Log.v(SERVICE_TAG, "Leaving behind " + mCurrentSpeechItem.mFilename); 622 if (tempFile.exists()) { 623 Log.v(SERVICE_TAG, "About to delete " 624 + mCurrentSpeechItem.mFilename); 625 if (tempFile.delete()) { 626 Log.v(SERVICE_TAG, "file successfully deleted"); 627 } 628 } 629 } 630 } 631 632 mCurrentSpeechItem = null; 633 } 634 } else { 635 Log.e(SERVICE_TAG, "TTS killAllUtterances(): queue locked longer than expected"); 636 result = TextToSpeech.ERROR; 637 } 638 } catch (InterruptedException e) { 639 Log.e(SERVICE_TAG, "TTS killAllUtterances(): tryLock interrupted"); 640 result = TextToSpeech.ERROR; 641 } finally { 642 // This check is needed because finally will always run, even if the 643 // method returns somewhere in the try block. 644 if (speechQueueAvailable) { 645 speechQueueLock.unlock(); 646 } 647 return result; 648 } 649 } 650 651 652 /** 653 * Stops all speech output and removes any utterances still in the queue globally, except 654 * those intended to be synthesized to file. 655 */ stopAll(String callingApp)656 private int stopAll(String callingApp) { 657 int result = TextToSpeech.ERROR; 658 boolean speechQueueAvailable = false; 659 try{ 660 speechQueueAvailable = 661 speechQueueLock.tryLock(SPEECHQUEUELOCK_TIMEOUT, TimeUnit.MILLISECONDS); 662 if (speechQueueAvailable) { 663 for (int i = mSpeechQueue.size() - 1; i > -1; i--){ 664 if (mSpeechQueue.get(i).mType != SpeechItem.TEXT_TO_FILE){ 665 mSpeechQueue.remove(i); 666 } 667 } 668 if ((mCurrentSpeechItem != null) && 669 ((mCurrentSpeechItem.mType != SpeechItem.TEXT_TO_FILE) || 670 mCurrentSpeechItem.mCallingApp.equals(callingApp))) { 671 try { 672 result = sNativeSynth.stop(); 673 } catch (NullPointerException e1) { 674 // synth will become null during onDestroy() 675 result = TextToSpeech.ERROR; 676 } 677 mKillList.put(mCurrentSpeechItem, true); 678 if (mPlayer != null) { 679 try { 680 mPlayer.stop(); 681 } catch (IllegalStateException e) { 682 // Do nothing, the player is already stopped. 683 } 684 } 685 mIsSpeaking = false; 686 mCurrentSpeechItem = null; 687 } else { 688 result = TextToSpeech.SUCCESS; 689 } 690 Log.i(SERVICE_TAG, "Stopped all"); 691 } else { 692 Log.e(SERVICE_TAG, "TTS stopAll(): queue locked longer than expected"); 693 result = TextToSpeech.ERROR; 694 } 695 } catch (InterruptedException e) { 696 Log.e(SERVICE_TAG, "TTS stopAll: tryLock interrupted"); 697 e.printStackTrace(); 698 } finally { 699 // This check is needed because finally will always run; even if the 700 // method returns somewhere in the try block. 701 if (speechQueueAvailable) { 702 speechQueueLock.unlock(); 703 } 704 return result; 705 } 706 } 707 onCompletion(MediaPlayer arg0)708 public void onCompletion(MediaPlayer arg0) { 709 // mCurrentSpeechItem may become null if it is stopped at the same 710 // time it completes. 711 SpeechItem currentSpeechItemCopy = mCurrentSpeechItem; 712 if (currentSpeechItemCopy != null) { 713 String callingApp = currentSpeechItemCopy.mCallingApp; 714 ArrayList<String> params = currentSpeechItemCopy.mParams; 715 String utteranceId = ""; 716 if (params != null) { 717 for (int i = 0; i < params.size() - 1; i = i + 2) { 718 String param = params.get(i); 719 if (param.equals(TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID)) { 720 utteranceId = params.get(i + 1); 721 } 722 } 723 } 724 if (utteranceId.length() > 0) { 725 dispatchUtteranceCompletedCallback(utteranceId, callingApp); 726 } 727 } 728 processSpeechQueue(); 729 } 730 playSilence(String callingApp, long duration, int queueMode, ArrayList<String> params)731 private int playSilence(String callingApp, long duration, int queueMode, 732 ArrayList<String> params) { 733 if (queueMode == TextToSpeech.QUEUE_FLUSH) { 734 stop(callingApp); 735 } 736 mSpeechQueue.add(new SpeechItem(callingApp, duration, params)); 737 if (!mIsSpeaking) { 738 processSpeechQueue(); 739 } 740 return TextToSpeech.SUCCESS; 741 } 742 silence(final SpeechItem speechItem)743 private void silence(final SpeechItem speechItem) { 744 class SilenceThread implements Runnable { 745 public void run() { 746 String utteranceId = ""; 747 if (speechItem.mParams != null){ 748 for (int i = 0; i < speechItem.mParams.size() - 1; i = i + 2){ 749 String param = speechItem.mParams.get(i); 750 if (param.equals(TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID)){ 751 utteranceId = speechItem.mParams.get(i+1); 752 } 753 } 754 } 755 try { 756 Thread.sleep(speechItem.mDuration); 757 } catch (InterruptedException e) { 758 e.printStackTrace(); 759 } finally { 760 if (utteranceId.length() > 0){ 761 dispatchUtteranceCompletedCallback(utteranceId, speechItem.mCallingApp); 762 } 763 processSpeechQueue(); 764 } 765 } 766 } 767 Thread slnc = (new Thread(new SilenceThread())); 768 slnc.setPriority(Thread.MIN_PRIORITY); 769 slnc.start(); 770 } 771 speakInternalOnly(final SpeechItem speechItem)772 private void speakInternalOnly(final SpeechItem speechItem) { 773 class SynthThread implements Runnable { 774 public void run() { 775 boolean synthAvailable = false; 776 String utteranceId = ""; 777 try { 778 synthAvailable = synthesizerLock.tryLock(); 779 if (!synthAvailable) { 780 mSynthBusy = true; 781 Thread.sleep(100); 782 Thread synth = (new Thread(new SynthThread())); 783 synth.start(); 784 mSynthBusy = false; 785 return; 786 } 787 int streamType = DEFAULT_STREAM_TYPE; 788 String language = ""; 789 String country = ""; 790 String variant = ""; 791 String speechRate = ""; 792 String engine = ""; 793 String pitch = ""; 794 if (speechItem.mParams != null){ 795 for (int i = 0; i < speechItem.mParams.size() - 1; i = i + 2){ 796 String param = speechItem.mParams.get(i); 797 if (param != null) { 798 if (param.equals(TextToSpeech.Engine.KEY_PARAM_RATE)) { 799 speechRate = speechItem.mParams.get(i+1); 800 } else if (param.equals(TextToSpeech.Engine.KEY_PARAM_LANGUAGE)){ 801 language = speechItem.mParams.get(i+1); 802 } else if (param.equals(TextToSpeech.Engine.KEY_PARAM_COUNTRY)){ 803 country = speechItem.mParams.get(i+1); 804 } else if (param.equals(TextToSpeech.Engine.KEY_PARAM_VARIANT)){ 805 variant = speechItem.mParams.get(i+1); 806 } else if (param.equals(TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID)){ 807 utteranceId = speechItem.mParams.get(i+1); 808 } else if (param.equals(TextToSpeech.Engine.KEY_PARAM_STREAM)) { 809 try { 810 streamType 811 = Integer.parseInt(speechItem.mParams.get(i + 1)); 812 } catch (NumberFormatException e) { 813 streamType = DEFAULT_STREAM_TYPE; 814 } 815 } else if (param.equals(TextToSpeech.Engine.KEY_PARAM_ENGINE)) { 816 engine = speechItem.mParams.get(i + 1); 817 } else if (param.equals(TextToSpeech.Engine.KEY_PARAM_PITCH)) { 818 pitch = speechItem.mParams.get(i + 1); 819 } 820 } 821 } 822 } 823 // Only do the synthesis if it has not been killed by a subsequent utterance. 824 if (mKillList.get(speechItem) == null) { 825 if (engine.length() > 0) { 826 setEngine(engine); 827 } else { 828 setEngine(getDefaultEngine()); 829 } 830 if (language.length() > 0){ 831 setLanguage("", language, country, variant); 832 } else { 833 setLanguage("", getDefaultLanguage(), getDefaultCountry(), 834 getDefaultLocVariant()); 835 } 836 if (speechRate.length() > 0){ 837 setSpeechRate("", Integer.parseInt(speechRate)); 838 } else { 839 setSpeechRate("", getDefaultRate()); 840 } 841 if (pitch.length() > 0){ 842 setPitch("", Integer.parseInt(pitch)); 843 } else { 844 setPitch("", getDefaultPitch()); 845 } 846 try { 847 sNativeSynth.speak(speechItem.mText, streamType); 848 } catch (NullPointerException e) { 849 // synth will become null during onDestroy() 850 Log.v(SERVICE_TAG, " null synth, can't speak"); 851 } 852 } 853 } catch (InterruptedException e) { 854 Log.e(SERVICE_TAG, "TTS speakInternalOnly(): tryLock interrupted"); 855 e.printStackTrace(); 856 } finally { 857 // This check is needed because finally will always run; 858 // even if the 859 // method returns somewhere in the try block. 860 if (utteranceId.length() > 0){ 861 dispatchUtteranceCompletedCallback(utteranceId, speechItem.mCallingApp); 862 } 863 if (synthAvailable) { 864 synthesizerLock.unlock(); 865 processSpeechQueue(); 866 } 867 } 868 } 869 } 870 Thread synth = (new Thread(new SynthThread())); 871 synth.setPriority(Thread.MAX_PRIORITY); 872 synth.start(); 873 } 874 synthToFileInternalOnly(final SpeechItem speechItem)875 private void synthToFileInternalOnly(final SpeechItem speechItem) { 876 class SynthThread implements Runnable { 877 public void run() { 878 boolean synthAvailable = false; 879 String utteranceId = ""; 880 Log.i(SERVICE_TAG, "Synthesizing to " + speechItem.mFilename); 881 try { 882 synthAvailable = synthesizerLock.tryLock(); 883 if (!synthAvailable) { 884 synchronized (this) { 885 mSynthBusy = true; 886 } 887 Thread.sleep(100); 888 Thread synth = (new Thread(new SynthThread())); 889 synth.start(); 890 synchronized (this) { 891 mSynthBusy = false; 892 } 893 return; 894 } 895 String language = ""; 896 String country = ""; 897 String variant = ""; 898 String speechRate = ""; 899 String engine = ""; 900 String pitch = ""; 901 if (speechItem.mParams != null){ 902 for (int i = 0; i < speechItem.mParams.size() - 1; i = i + 2){ 903 String param = speechItem.mParams.get(i); 904 if (param != null) { 905 if (param.equals(TextToSpeech.Engine.KEY_PARAM_RATE)) { 906 speechRate = speechItem.mParams.get(i+1); 907 } else if (param.equals(TextToSpeech.Engine.KEY_PARAM_LANGUAGE)){ 908 language = speechItem.mParams.get(i+1); 909 } else if (param.equals(TextToSpeech.Engine.KEY_PARAM_COUNTRY)){ 910 country = speechItem.mParams.get(i+1); 911 } else if (param.equals(TextToSpeech.Engine.KEY_PARAM_VARIANT)){ 912 variant = speechItem.mParams.get(i+1); 913 } else if (param.equals(TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID)){ 914 utteranceId = speechItem.mParams.get(i+1); 915 } else if (param.equals(TextToSpeech.Engine.KEY_PARAM_ENGINE)) { 916 engine = speechItem.mParams.get(i + 1); 917 } else if (param.equals(TextToSpeech.Engine.KEY_PARAM_PITCH)) { 918 pitch = speechItem.mParams.get(i + 1); 919 } 920 } 921 } 922 } 923 // Only do the synthesis if it has not been killed by a subsequent utterance. 924 if (mKillList.get(speechItem) == null){ 925 if (engine.length() > 0) { 926 setEngine(engine); 927 } else { 928 setEngine(getDefaultEngine()); 929 } 930 if (language.length() > 0){ 931 setLanguage("", language, country, variant); 932 } else { 933 setLanguage("", getDefaultLanguage(), getDefaultCountry(), 934 getDefaultLocVariant()); 935 } 936 if (speechRate.length() > 0){ 937 setSpeechRate("", Integer.parseInt(speechRate)); 938 } else { 939 setSpeechRate("", getDefaultRate()); 940 } 941 if (pitch.length() > 0){ 942 setPitch("", Integer.parseInt(pitch)); 943 } else { 944 setPitch("", getDefaultPitch()); 945 } 946 try { 947 sNativeSynth.synthesizeToFile(speechItem.mText, speechItem.mFilename); 948 } catch (NullPointerException e) { 949 // synth will become null during onDestroy() 950 Log.v(SERVICE_TAG, " null synth, can't synthesize to file"); 951 } 952 } 953 } catch (InterruptedException e) { 954 Log.e(SERVICE_TAG, "TTS synthToFileInternalOnly(): tryLock interrupted"); 955 e.printStackTrace(); 956 } finally { 957 // This check is needed because finally will always run; 958 // even if the 959 // method returns somewhere in the try block. 960 if (utteranceId.length() > 0){ 961 dispatchUtteranceCompletedCallback(utteranceId, speechItem.mCallingApp); 962 } 963 if (synthAvailable) { 964 synthesizerLock.unlock(); 965 processSpeechQueue(); 966 } 967 } 968 } 969 } 970 Thread synth = (new Thread(new SynthThread())); 971 synth.setPriority(Thread.MAX_PRIORITY); 972 synth.start(); 973 } 974 getSoundResource(SpeechItem speechItem)975 private SoundResource getSoundResource(SpeechItem speechItem) { 976 SoundResource sr = null; 977 String text = speechItem.mText; 978 if (speechItem.mType == SpeechItem.SILENCE) { 979 // Do nothing if this is just silence 980 } else if (speechItem.mType == SpeechItem.EARCON) { 981 sr = mEarcons.get(text); 982 } else { 983 sr = mUtterances.get(text); 984 } 985 return sr; 986 } 987 broadcastTtsQueueProcessingCompleted()988 private void broadcastTtsQueueProcessingCompleted(){ 989 Intent i = new Intent(TextToSpeech.ACTION_TTS_QUEUE_PROCESSING_COMPLETED); 990 sendBroadcast(i); 991 } 992 993 dispatchUtteranceCompletedCallback(String utteranceId, String packageName)994 private void dispatchUtteranceCompletedCallback(String utteranceId, String packageName) { 995 ITtsCallback cb = mCallbacksMap.get(packageName); 996 if (cb == null){ 997 return; 998 } 999 Log.v(SERVICE_TAG, "TTS callback: dispatch started"); 1000 // Broadcast to all clients the new value. 1001 final int N = mCallbacks.beginBroadcast(); 1002 try { 1003 cb.utteranceCompleted(utteranceId); 1004 } catch (RemoteException e) { 1005 // The RemoteCallbackList will take care of removing 1006 // the dead object for us. 1007 } 1008 mCallbacks.finishBroadcast(); 1009 Log.v(SERVICE_TAG, "TTS callback: dispatch completed to " + N); 1010 } 1011 splitCurrentTextIfNeeded(SpeechItem currentSpeechItem)1012 private SpeechItem splitCurrentTextIfNeeded(SpeechItem currentSpeechItem){ 1013 if (currentSpeechItem.mText.length() < MAX_SPEECH_ITEM_CHAR_LENGTH){ 1014 return currentSpeechItem; 1015 } else { 1016 String callingApp = currentSpeechItem.mCallingApp; 1017 ArrayList<SpeechItem> splitItems = new ArrayList<SpeechItem>(); 1018 int start = 0; 1019 int end = start + MAX_SPEECH_ITEM_CHAR_LENGTH - 1; 1020 String splitText; 1021 SpeechItem splitItem; 1022 while (end < currentSpeechItem.mText.length()){ 1023 splitText = currentSpeechItem.mText.substring(start, end); 1024 splitItem = new SpeechItem(callingApp, splitText, null, SpeechItem.TEXT); 1025 splitItems.add(splitItem); 1026 start = end; 1027 end = start + MAX_SPEECH_ITEM_CHAR_LENGTH - 1; 1028 } 1029 splitText = currentSpeechItem.mText.substring(start); 1030 splitItem = new SpeechItem(callingApp, splitText, null, SpeechItem.TEXT); 1031 splitItems.add(splitItem); 1032 mSpeechQueue.remove(0); 1033 for (int i = splitItems.size() - 1; i >= 0; i--){ 1034 mSpeechQueue.add(0, splitItems.get(i)); 1035 } 1036 return mSpeechQueue.get(0); 1037 } 1038 } 1039 processSpeechQueue()1040 private void processSpeechQueue() { 1041 boolean speechQueueAvailable = false; 1042 synchronized (this) { 1043 if (mSynthBusy){ 1044 // There is already a synth thread waiting to run. 1045 return; 1046 } 1047 } 1048 try { 1049 speechQueueAvailable = 1050 speechQueueLock.tryLock(SPEECHQUEUELOCK_TIMEOUT, TimeUnit.MILLISECONDS); 1051 if (!speechQueueAvailable) { 1052 Log.e(SERVICE_TAG, "processSpeechQueue - Speech queue is unavailable."); 1053 return; 1054 } 1055 if (mSpeechQueue.size() < 1) { 1056 mIsSpeaking = false; 1057 mKillList.clear(); 1058 broadcastTtsQueueProcessingCompleted(); 1059 return; 1060 } 1061 1062 mCurrentSpeechItem = mSpeechQueue.get(0); 1063 mIsSpeaking = true; 1064 SoundResource sr = getSoundResource(mCurrentSpeechItem); 1065 // Synth speech as needed - synthesizer should call 1066 // processSpeechQueue to continue running the queue 1067 Log.v(SERVICE_TAG, "TTS processing: " + mCurrentSpeechItem.mText); 1068 if (sr == null) { 1069 if (mCurrentSpeechItem.mType == SpeechItem.TEXT) { 1070 mCurrentSpeechItem = splitCurrentTextIfNeeded(mCurrentSpeechItem); 1071 speakInternalOnly(mCurrentSpeechItem); 1072 } else if (mCurrentSpeechItem.mType == SpeechItem.TEXT_TO_FILE) { 1073 synthToFileInternalOnly(mCurrentSpeechItem); 1074 } else { 1075 // This is either silence or an earcon that was missing 1076 silence(mCurrentSpeechItem); 1077 } 1078 } else { 1079 cleanUpPlayer(); 1080 if (sr.mSourcePackageName == PKGNAME) { 1081 // Utterance is part of the TTS library 1082 mPlayer = MediaPlayer.create(this, sr.mResId); 1083 } else if (sr.mSourcePackageName != null) { 1084 // Utterance is part of the app calling the library 1085 Context ctx; 1086 try { 1087 ctx = this.createPackageContext(sr.mSourcePackageName, 0); 1088 } catch (NameNotFoundException e) { 1089 e.printStackTrace(); 1090 mSpeechQueue.remove(0); // Remove it from the queue and 1091 // move on 1092 mIsSpeaking = false; 1093 return; 1094 } 1095 mPlayer = MediaPlayer.create(ctx, sr.mResId); 1096 } else { 1097 // Utterance is coming from a file 1098 mPlayer = MediaPlayer.create(this, Uri.parse(sr.mFilename)); 1099 } 1100 1101 // Check if Media Server is dead; if it is, clear the queue and 1102 // give up for now - hopefully, it will recover itself. 1103 if (mPlayer == null) { 1104 mSpeechQueue.clear(); 1105 mIsSpeaking = false; 1106 return; 1107 } 1108 mPlayer.setOnCompletionListener(this); 1109 try { 1110 mPlayer.setAudioStreamType(getStreamTypeFromParams(mCurrentSpeechItem.mParams)); 1111 mPlayer.start(); 1112 } catch (IllegalStateException e) { 1113 mSpeechQueue.clear(); 1114 mIsSpeaking = false; 1115 cleanUpPlayer(); 1116 return; 1117 } 1118 } 1119 if (mSpeechQueue.size() > 0) { 1120 mSpeechQueue.remove(0); 1121 } 1122 } catch (InterruptedException e) { 1123 Log.e(SERVICE_TAG, "TTS processSpeechQueue: tryLock interrupted"); 1124 e.printStackTrace(); 1125 } finally { 1126 // This check is needed because finally will always run; even if the 1127 // method returns somewhere in the try block. 1128 if (speechQueueAvailable) { 1129 speechQueueLock.unlock(); 1130 } 1131 } 1132 } 1133 getStreamTypeFromParams(ArrayList<String> paramList)1134 private int getStreamTypeFromParams(ArrayList<String> paramList) { 1135 int streamType = DEFAULT_STREAM_TYPE; 1136 if (paramList == null) { 1137 return streamType; 1138 } 1139 for (int i = 0; i < paramList.size() - 1; i = i + 2) { 1140 String param = paramList.get(i); 1141 if ((param != null) && (param.equals(TextToSpeech.Engine.KEY_PARAM_STREAM))) { 1142 try { 1143 streamType = Integer.parseInt(paramList.get(i + 1)); 1144 } catch (NumberFormatException e) { 1145 streamType = DEFAULT_STREAM_TYPE; 1146 } 1147 } 1148 } 1149 return streamType; 1150 } 1151 cleanUpPlayer()1152 private void cleanUpPlayer() { 1153 if (mPlayer != null) { 1154 mPlayer.release(); 1155 mPlayer = null; 1156 } 1157 } 1158 1159 /** 1160 * Synthesizes the given text to a file using the specified parameters. 1161 * 1162 * @param text 1163 * The String of text that should be synthesized 1164 * @param params 1165 * An ArrayList of parameters. The first element of this array 1166 * controls the type of voice to use. 1167 * @param filename 1168 * The string that gives the full output filename; it should be 1169 * something like "/sdcard/myappsounds/mysound.wav". 1170 * @return A boolean that indicates if the synthesis can be started 1171 */ synthesizeToFile(String callingApp, String text, ArrayList<String> params, String filename)1172 private boolean synthesizeToFile(String callingApp, String text, ArrayList<String> params, 1173 String filename) { 1174 // Don't allow a filename that is too long 1175 if (filename.length() > MAX_FILENAME_LENGTH) { 1176 return false; 1177 } 1178 // Don't allow anything longer than the max text length; since this 1179 // is synthing to a file, don't even bother splitting it. 1180 if (text.length() >= MAX_SPEECH_ITEM_CHAR_LENGTH){ 1181 return false; 1182 } 1183 // Check that the output file can be created 1184 try { 1185 File tempFile = new File(filename); 1186 if (tempFile.exists()) { 1187 Log.v("TtsService", "File " + filename + " exists, deleting."); 1188 tempFile.delete(); 1189 } 1190 if (!tempFile.createNewFile()) { 1191 Log.e("TtsService", "Unable to synthesize to file: can't create " + filename); 1192 return false; 1193 } 1194 tempFile.delete(); 1195 } catch (IOException e) { 1196 Log.e("TtsService", "Can't create " + filename + " due to exception " + e); 1197 return false; 1198 } 1199 mSpeechQueue.add(new SpeechItem(callingApp, text, params, SpeechItem.TEXT_TO_FILE, filename)); 1200 if (!mIsSpeaking) { 1201 processSpeechQueue(); 1202 } 1203 return true; 1204 } 1205 1206 @Override onBind(Intent intent)1207 public IBinder onBind(Intent intent) { 1208 if (ACTION.equals(intent.getAction())) { 1209 for (String category : intent.getCategories()) { 1210 if (category.equals(CATEGORY)) { 1211 return mBinder; 1212 } 1213 } 1214 } 1215 return null; 1216 } 1217 1218 private final android.speech.tts.ITts.Stub mBinder = new Stub() { 1219 1220 public int registerCallback(String packageName, ITtsCallback cb) { 1221 if (cb != null) { 1222 mCallbacks.register(cb); 1223 mCallbacksMap.put(packageName, cb); 1224 return TextToSpeech.SUCCESS; 1225 } 1226 return TextToSpeech.ERROR; 1227 } 1228 1229 public int unregisterCallback(String packageName, ITtsCallback cb) { 1230 if (cb != null) { 1231 mCallbacksMap.remove(packageName); 1232 mCallbacks.unregister(cb); 1233 return TextToSpeech.SUCCESS; 1234 } 1235 return TextToSpeech.ERROR; 1236 } 1237 1238 /** 1239 * Speaks the given text using the specified queueing mode and 1240 * parameters. 1241 * 1242 * @param text 1243 * The text that should be spoken 1244 * @param queueMode 1245 * TextToSpeech.TTS_QUEUE_FLUSH for no queue (interrupts all previous utterances) 1246 * TextToSpeech.TTS_QUEUE_ADD for queued 1247 * @param params 1248 * An ArrayList of parameters. The first element of this 1249 * array controls the type of voice to use. 1250 */ 1251 public int speak(String callingApp, String text, int queueMode, String[] params) { 1252 ArrayList<String> speakingParams = new ArrayList<String>(); 1253 if (params != null) { 1254 speakingParams = new ArrayList<String>(Arrays.asList(params)); 1255 } 1256 return mSelf.speak(callingApp, text, queueMode, speakingParams); 1257 } 1258 1259 /** 1260 * Plays the earcon using the specified queueing mode and parameters. 1261 * 1262 * @param earcon 1263 * The earcon that should be played 1264 * @param queueMode 1265 * TextToSpeech.TTS_QUEUE_FLUSH for no queue (interrupts all previous utterances) 1266 * TextToSpeech.TTS_QUEUE_ADD for queued 1267 * @param params 1268 * An ArrayList of parameters. 1269 */ 1270 public int playEarcon(String callingApp, String earcon, int queueMode, String[] params) { 1271 ArrayList<String> speakingParams = new ArrayList<String>(); 1272 if (params != null) { 1273 speakingParams = new ArrayList<String>(Arrays.asList(params)); 1274 } 1275 return mSelf.playEarcon(callingApp, earcon, queueMode, speakingParams); 1276 } 1277 1278 /** 1279 * Plays the silence using the specified queueing mode and parameters. 1280 * 1281 * @param duration 1282 * The duration of the silence that should be played 1283 * @param queueMode 1284 * TextToSpeech.TTS_QUEUE_FLUSH for no queue (interrupts all previous utterances) 1285 * TextToSpeech.TTS_QUEUE_ADD for queued 1286 * @param params 1287 * An ArrayList of parameters. 1288 */ 1289 public int playSilence(String callingApp, long duration, int queueMode, String[] params) { 1290 ArrayList<String> speakingParams = new ArrayList<String>(); 1291 if (params != null) { 1292 speakingParams = new ArrayList<String>(Arrays.asList(params)); 1293 } 1294 return mSelf.playSilence(callingApp, duration, queueMode, speakingParams); 1295 } 1296 1297 /** 1298 * Stops all speech output and removes any utterances still in the 1299 * queue. 1300 */ 1301 public int stop(String callingApp) { 1302 return mSelf.stop(callingApp); 1303 } 1304 1305 /** 1306 * Returns whether or not the TTS is speaking. 1307 * 1308 * @return Boolean to indicate whether or not the TTS is speaking 1309 */ 1310 public boolean isSpeaking() { 1311 return (mSelf.mIsSpeaking && (mSpeechQueue.size() < 1)); 1312 } 1313 1314 /** 1315 * Adds a sound resource to the TTS. 1316 * 1317 * @param text 1318 * The text that should be associated with the sound resource 1319 * @param packageName 1320 * The name of the package which has the sound resource 1321 * @param resId 1322 * The resource ID of the sound within its package 1323 */ 1324 public void addSpeech(String callingApp, String text, String packageName, int resId) { 1325 mSelf.addSpeech(callingApp, text, packageName, resId); 1326 } 1327 1328 /** 1329 * Adds a sound resource to the TTS. 1330 * 1331 * @param text 1332 * The text that should be associated with the sound resource 1333 * @param filename 1334 * The filename of the sound resource. This must be a 1335 * complete path like: (/sdcard/mysounds/mysoundbite.mp3). 1336 */ 1337 public void addSpeechFile(String callingApp, String text, String filename) { 1338 mSelf.addSpeech(callingApp, text, filename); 1339 } 1340 1341 /** 1342 * Adds a sound resource to the TTS as an earcon. 1343 * 1344 * @param earcon 1345 * The text that should be associated with the sound resource 1346 * @param packageName 1347 * The name of the package which has the sound resource 1348 * @param resId 1349 * The resource ID of the sound within its package 1350 */ 1351 public void addEarcon(String callingApp, String earcon, String packageName, int resId) { 1352 mSelf.addEarcon(callingApp, earcon, packageName, resId); 1353 } 1354 1355 /** 1356 * Adds a sound resource to the TTS as an earcon. 1357 * 1358 * @param earcon 1359 * The text that should be associated with the sound resource 1360 * @param filename 1361 * The filename of the sound resource. This must be a 1362 * complete path like: (/sdcard/mysounds/mysoundbite.mp3). 1363 */ 1364 public void addEarconFile(String callingApp, String earcon, String filename) { 1365 mSelf.addEarcon(callingApp, earcon, filename); 1366 } 1367 1368 /** 1369 * Sets the speech rate for the TTS. Note that this will only have an 1370 * effect on synthesized speech; it will not affect pre-recorded speech. 1371 * 1372 * @param speechRate 1373 * The speech rate that should be used 1374 */ 1375 public int setSpeechRate(String callingApp, int speechRate) { 1376 return mSelf.setSpeechRate(callingApp, speechRate); 1377 } 1378 1379 /** 1380 * Sets the pitch for the TTS. Note that this will only have an 1381 * effect on synthesized speech; it will not affect pre-recorded speech. 1382 * 1383 * @param pitch 1384 * The pitch that should be used for the synthesized voice 1385 */ 1386 public int setPitch(String callingApp, int pitch) { 1387 return mSelf.setPitch(callingApp, pitch); 1388 } 1389 1390 /** 1391 * Returns the level of support for the specified language. 1392 * 1393 * @param lang the three letter ISO language code. 1394 * @param country the three letter ISO country code. 1395 * @param variant the variant code associated with the country and language pair. 1396 * @return one of TTS_LANG_NOT_SUPPORTED, TTS_LANG_MISSING_DATA, TTS_LANG_AVAILABLE, 1397 * TTS_LANG_COUNTRY_AVAILABLE, TTS_LANG_COUNTRY_VAR_AVAILABLE as defined in 1398 * android.speech.tts.TextToSpeech. 1399 */ 1400 public int isLanguageAvailable(String lang, String country, String variant, 1401 String[] params) { 1402 for (int i = 0; i < params.length - 1; i = i + 2){ 1403 String param = params[i]; 1404 if (param != null) { 1405 if (param.equals(TextToSpeech.Engine.KEY_PARAM_ENGINE)) { 1406 mSelf.setEngine(params[i + 1]); 1407 break; 1408 } 1409 } 1410 } 1411 return mSelf.isLanguageAvailable(lang, country, variant); 1412 } 1413 1414 /** 1415 * Returns the currently set language / country / variant strings representing the 1416 * language used by the TTS engine. 1417 * @return null is no language is set, or an array of 3 string containing respectively 1418 * the language, country and variant. 1419 */ 1420 public String[] getLanguage() { 1421 return mSelf.getLanguage(); 1422 } 1423 1424 /** 1425 * Sets the speech rate for the TTS, which affects the synthesized voice. 1426 * 1427 * @param lang the three letter ISO language code. 1428 * @param country the three letter ISO country code. 1429 * @param variant the variant code associated with the country and language pair. 1430 */ 1431 public int setLanguage(String callingApp, String lang, String country, String variant) { 1432 return mSelf.setLanguage(callingApp, lang, country, variant); 1433 } 1434 1435 /** 1436 * Synthesizes the given text to a file using the specified 1437 * parameters. 1438 * 1439 * @param text 1440 * The String of text that should be synthesized 1441 * @param params 1442 * An ArrayList of parameters. The first element of this 1443 * array controls the type of voice to use. 1444 * @param filename 1445 * The string that gives the full output filename; it should 1446 * be something like "/sdcard/myappsounds/mysound.wav". 1447 * @return A boolean that indicates if the synthesis succeeded 1448 */ 1449 public boolean synthesizeToFile(String callingApp, String text, String[] params, 1450 String filename) { 1451 ArrayList<String> speakingParams = new ArrayList<String>(); 1452 if (params != null) { 1453 speakingParams = new ArrayList<String>(Arrays.asList(params)); 1454 } 1455 return mSelf.synthesizeToFile(callingApp, text, speakingParams, filename); 1456 } 1457 1458 /** 1459 * Sets the speech synthesis engine for the TTS by specifying its packagename 1460 * 1461 * @param packageName the packageName of the speech synthesis engine (ie, "com.svox.pico") 1462 * 1463 * @return SUCCESS or ERROR as defined in android.speech.tts.TextToSpeech. 1464 */ 1465 public int setEngineByPackageName(String packageName) { 1466 return mSelf.setEngine(packageName); 1467 } 1468 1469 /** 1470 * Returns the packagename of the default speech synthesis engine. 1471 * 1472 * @return Packagename of the TTS engine that the user has chosen as their default. 1473 */ 1474 public String getDefaultEngine() { 1475 return mSelf.getDefaultEngine(); 1476 } 1477 1478 /** 1479 * Returns whether or not the user is forcing their defaults to override the 1480 * Text-To-Speech settings set by applications. 1481 * 1482 * @return Whether or not defaults are enforced. 1483 */ 1484 public boolean areDefaultsEnforced() { 1485 return mSelf.isDefaultEnforced(); 1486 } 1487 1488 }; 1489 1490 } 1491