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