TextToSpeech.java revision 4bbca889df9ca76c398f3a11e871fc6ad4a4514d
1/* 2 * Copyright (C) 2009 The Android Open Source Project 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.speech.tts; 17 18import android.annotation.SdkConstant; 19import android.annotation.SdkConstant.SdkConstantType; 20import android.content.ComponentName; 21import android.content.ContentResolver; 22import android.content.Context; 23import android.content.Intent; 24import android.content.ServiceConnection; 25import android.content.pm.PackageManager; 26import android.content.pm.ResolveInfo; 27import android.content.pm.ServiceInfo; 28import android.media.AudioManager; 29import android.net.Uri; 30import android.os.Bundle; 31import android.os.IBinder; 32import android.os.RemoteException; 33import android.provider.Settings; 34import android.text.TextUtils; 35import android.util.Log; 36 37import java.util.ArrayList; 38import java.util.Collections; 39import java.util.HashMap; 40import java.util.List; 41import java.util.Locale; 42import java.util.Map; 43 44/** 45 * 46 * Synthesizes speech from text for immediate playback or to create a sound file. 47 * <p>A TextToSpeech instance can only be used to synthesize text once it has completed its 48 * initialization. Implement the {@link TextToSpeech.OnInitListener} to be 49 * notified of the completion of the initialization.<br> 50 * When you are done using the TextToSpeech instance, call the {@link #shutdown()} method 51 * to release the native resources used by the TextToSpeech engine. 52 * 53 */ 54public class TextToSpeech { 55 56 private static final String TAG = "TextToSpeech"; 57 58 /** 59 * Denotes a successful operation. 60 */ 61 public static final int SUCCESS = 0; 62 /** 63 * Denotes a generic operation failure. 64 */ 65 public static final int ERROR = -1; 66 67 /** 68 * Queue mode where all entries in the playback queue (media to be played 69 * and text to be synthesized) are dropped and replaced by the new entry. 70 */ 71 public static final int QUEUE_FLUSH = 0; 72 /** 73 * Queue mode where the new entry is added at the end of the playback queue. 74 */ 75 public static final int QUEUE_ADD = 1; 76 77 /** 78 * Denotes the language is available exactly as specified by the locale. 79 */ 80 public static final int LANG_COUNTRY_VAR_AVAILABLE = 2; 81 82 /** 83 * Denotes the language is available for the language and country specified 84 * by the locale, but not the variant. 85 */ 86 public static final int LANG_COUNTRY_AVAILABLE = 1; 87 88 /** 89 * Denotes the language is available for the language by the locale, 90 * but not the country and variant. 91 */ 92 public static final int LANG_AVAILABLE = 0; 93 94 /** 95 * Denotes the language data is missing. 96 */ 97 public static final int LANG_MISSING_DATA = -1; 98 99 /** 100 * Denotes the language is not supported. 101 */ 102 public static final int LANG_NOT_SUPPORTED = -2; 103 104 /** 105 * Broadcast Action: The TextToSpeech synthesizer has completed processing 106 * of all the text in the speech queue. 107 */ 108 @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) 109 public static final String ACTION_TTS_QUEUE_PROCESSING_COMPLETED = 110 "android.speech.tts.TTS_QUEUE_PROCESSING_COMPLETED"; 111 112 /** 113 * Interface definition of a callback to be invoked indicating the completion of the 114 * TextToSpeech engine initialization. 115 */ 116 public interface OnInitListener { 117 /** 118 * Called to signal the completion of the TextToSpeech engine initialization. 119 * 120 * @param status {@link TextToSpeech#SUCCESS} or {@link TextToSpeech#ERROR}. 121 */ 122 public void onInit(int status); 123 } 124 125 /** 126 * Listener that will be called when the TTS service has 127 * completed synthesizing an utterance. This is only called if the utterance 128 * has an utterance ID (see {@link TextToSpeech.Engine#KEY_PARAM_UTTERANCE_ID}). 129 */ 130 public interface OnUtteranceCompletedListener { 131 /** 132 * Called when an utterance has been synthesized. 133 * 134 * @param utteranceId the identifier of the utterance. 135 */ 136 public void onUtteranceCompleted(String utteranceId); 137 } 138 139 /** 140 * Constants and parameter names for controlling text-to-speech. 141 */ 142 public class Engine { 143 144 /** 145 * Default speech rate. 146 * @hide 147 */ 148 public static final int DEFAULT_RATE = 100; 149 150 /** 151 * Default pitch. 152 * @hide 153 */ 154 public static final int DEFAULT_PITCH = 100; 155 156 /** 157 * Default volume. 158 * @hide 159 */ 160 public static final float DEFAULT_VOLUME = 1.0f; 161 162 /** 163 * Default pan (centered). 164 * @hide 165 */ 166 public static final float DEFAULT_PAN = 0.0f; 167 168 /** 169 * Default value for {@link Settings.Secure#TTS_USE_DEFAULTS}. 170 * @hide 171 */ 172 public static final int USE_DEFAULTS = 0; // false 173 174 /** 175 * Package name of the default TTS engine. 176 * 177 * TODO: This should come from a system property 178 * 179 * @hide 180 */ 181 public static final String DEFAULT_ENGINE = "com.svox.pico"; 182 183 /** 184 * Default audio stream used when playing synthesized speech. 185 */ 186 public static final int DEFAULT_STREAM = AudioManager.STREAM_MUSIC; 187 188 /** 189 * Indicates success when checking the installation status of the resources used by the 190 * TextToSpeech engine with the {@link #ACTION_CHECK_TTS_DATA} intent. 191 */ 192 public static final int CHECK_VOICE_DATA_PASS = 1; 193 194 /** 195 * Indicates failure when checking the installation status of the resources used by the 196 * TextToSpeech engine with the {@link #ACTION_CHECK_TTS_DATA} intent. 197 */ 198 public static final int CHECK_VOICE_DATA_FAIL = 0; 199 200 /** 201 * Indicates erroneous data when checking the installation status of the resources used by 202 * the TextToSpeech engine with the {@link #ACTION_CHECK_TTS_DATA} intent. 203 */ 204 public static final int CHECK_VOICE_DATA_BAD_DATA = -1; 205 206 /** 207 * Indicates missing resources when checking the installation status of the resources used 208 * by the TextToSpeech engine with the {@link #ACTION_CHECK_TTS_DATA} intent. 209 */ 210 public static final int CHECK_VOICE_DATA_MISSING_DATA = -2; 211 212 /** 213 * Indicates missing storage volume when checking the installation status of the resources 214 * used by the TextToSpeech engine with the {@link #ACTION_CHECK_TTS_DATA} intent. 215 */ 216 public static final int CHECK_VOICE_DATA_MISSING_VOLUME = -3; 217 218 /** 219 * Intent for starting a TTS service. Services that handle this intent must 220 * extend {@link TextToSpeechService}. Normal applications should not use this intent 221 * directly, instead they should talk to the TTS service using the the methods in this 222 * class. 223 */ 224 @SdkConstant(SdkConstantType.SERVICE_ACTION) 225 public static final String INTENT_ACTION_TTS_SERVICE = 226 "android.intent.action.TTS_SERVICE"; 227 228 // intents to ask engine to install data or check its data 229 /** 230 * Activity Action: Triggers the platform TextToSpeech engine to 231 * start the activity that installs the resource files on the device 232 * that are required for TTS to be operational. Since the installation 233 * of the data can be interrupted or declined by the user, the application 234 * shouldn't expect successful installation upon return from that intent, 235 * and if need be, should check installation status with 236 * {@link #ACTION_CHECK_TTS_DATA}. 237 */ 238 @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) 239 public static final String ACTION_INSTALL_TTS_DATA = 240 "android.speech.tts.engine.INSTALL_TTS_DATA"; 241 242 /** 243 * Broadcast Action: broadcast to signal the completion of the installation of 244 * the data files used by the synthesis engine. Success or failure is indicated in the 245 * {@link #EXTRA_TTS_DATA_INSTALLED} extra. 246 */ 247 @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) 248 public static final String ACTION_TTS_DATA_INSTALLED = 249 "android.speech.tts.engine.TTS_DATA_INSTALLED"; 250 251 /** 252 * Activity Action: Starts the activity from the platform TextToSpeech 253 * engine to verify the proper installation and availability of the 254 * resource files on the system. Upon completion, the activity will 255 * return one of the following codes: 256 * {@link #CHECK_VOICE_DATA_PASS}, 257 * {@link #CHECK_VOICE_DATA_FAIL}, 258 * {@link #CHECK_VOICE_DATA_BAD_DATA}, 259 * {@link #CHECK_VOICE_DATA_MISSING_DATA}, or 260 * {@link #CHECK_VOICE_DATA_MISSING_VOLUME}. 261 * <p> Moreover, the data received in the activity result will contain the following 262 * fields: 263 * <ul> 264 * <li>{@link #EXTRA_VOICE_DATA_ROOT_DIRECTORY} which 265 * indicates the path to the location of the resource files,</li> 266 * <li>{@link #EXTRA_VOICE_DATA_FILES} which contains 267 * the list of all the resource files,</li> 268 * <li>and {@link #EXTRA_VOICE_DATA_FILES_INFO} which 269 * contains, for each resource file, the description of the language covered by 270 * the file in the xxx-YYY format, where xxx is the 3-letter ISO language code, 271 * and YYY is the 3-letter ISO country code.</li> 272 * </ul> 273 */ 274 @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) 275 public static final String ACTION_CHECK_TTS_DATA = 276 "android.speech.tts.engine.CHECK_TTS_DATA"; 277 278 /** 279 * Activity intent for getting some sample text to use for demonstrating TTS. 280 * 281 * @hide This intent was used by engines written against the old API. 282 * Not sure if it should be exposed. 283 */ 284 @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) 285 public static final String ACTION_GET_SAMPLE_TEXT = 286 "android.speech.tts.engine.GET_SAMPLE_TEXT"; 287 288 // extras for a TTS engine's check data activity 289 /** 290 * Extra information received with the {@link #ACTION_CHECK_TTS_DATA} intent where 291 * the TextToSpeech engine specifies the path to its resources. 292 */ 293 public static final String EXTRA_VOICE_DATA_ROOT_DIRECTORY = "dataRoot"; 294 295 /** 296 * Extra information received with the {@link #ACTION_CHECK_TTS_DATA} intent where 297 * the TextToSpeech engine specifies the file names of its resources under the 298 * resource path. 299 */ 300 public static final String EXTRA_VOICE_DATA_FILES = "dataFiles"; 301 302 /** 303 * Extra information received with the {@link #ACTION_CHECK_TTS_DATA} intent where 304 * the TextToSpeech engine specifies the locale associated with each resource file. 305 */ 306 public static final String EXTRA_VOICE_DATA_FILES_INFO = "dataFilesInfo"; 307 308 /** 309 * Extra information received with the {@link #ACTION_CHECK_TTS_DATA} intent where 310 * the TextToSpeech engine returns an ArrayList<String> of all the available voices. 311 * The format of each voice is: lang-COUNTRY-variant where COUNTRY and variant are 312 * optional (ie, "eng" or "eng-USA" or "eng-USA-FEMALE"). 313 */ 314 public static final String EXTRA_AVAILABLE_VOICES = "availableVoices"; 315 316 /** 317 * Extra information received with the {@link #ACTION_CHECK_TTS_DATA} intent where 318 * the TextToSpeech engine returns an ArrayList<String> of all the unavailable voices. 319 * The format of each voice is: lang-COUNTRY-variant where COUNTRY and variant are 320 * optional (ie, "eng" or "eng-USA" or "eng-USA-FEMALE"). 321 */ 322 public static final String EXTRA_UNAVAILABLE_VOICES = "unavailableVoices"; 323 324 /** 325 * Extra information sent with the {@link #ACTION_CHECK_TTS_DATA} intent where the 326 * caller indicates to the TextToSpeech engine which specific sets of voice data to 327 * check for by sending an ArrayList<String> of the voices that are of interest. 328 * The format of each voice is: lang-COUNTRY-variant where COUNTRY and variant are 329 * optional (ie, "eng" or "eng-USA" or "eng-USA-FEMALE"). 330 */ 331 public static final String EXTRA_CHECK_VOICE_DATA_FOR = "checkVoiceDataFor"; 332 333 // extras for a TTS engine's data installation 334 /** 335 * Extra information received with the {@link #ACTION_TTS_DATA_INSTALLED} intent. 336 * It indicates whether the data files for the synthesis engine were successfully 337 * installed. The installation was initiated with the {@link #ACTION_INSTALL_TTS_DATA} 338 * intent. The possible values for this extra are 339 * {@link TextToSpeech#SUCCESS} and {@link TextToSpeech#ERROR}. 340 */ 341 public static final String EXTRA_TTS_DATA_INSTALLED = "dataInstalled"; 342 343 // keys for the parameters passed with speak commands. Hidden keys are used internally 344 // to maintain engine state for each TextToSpeech instance. 345 /** 346 * @hide 347 */ 348 public static final String KEY_PARAM_RATE = "rate"; 349 350 /** 351 * @hide 352 */ 353 public static final String KEY_PARAM_LANGUAGE = "language"; 354 355 /** 356 * @hide 357 */ 358 public static final String KEY_PARAM_COUNTRY = "country"; 359 360 /** 361 * @hide 362 */ 363 public static final String KEY_PARAM_VARIANT = "variant"; 364 365 /** 366 * @hide 367 */ 368 public static final String KEY_PARAM_ENGINE = "engine"; 369 370 /** 371 * @hide 372 */ 373 public static final String KEY_PARAM_PITCH = "pitch"; 374 375 /** 376 * Parameter key to specify the audio stream type to be used when speaking text 377 * or playing back a file. The value should be one of the STREAM_ constants 378 * defined in {@link AudioManager}. 379 * 380 * @see TextToSpeech#speak(String, int, HashMap) 381 * @see TextToSpeech#playEarcon(String, int, HashMap) 382 */ 383 public static final String KEY_PARAM_STREAM = "streamType"; 384 385 /** 386 * Parameter key to identify an utterance in the 387 * {@link TextToSpeech.OnUtteranceCompletedListener} after text has been 388 * spoken, a file has been played back or a silence duration has elapsed. 389 * 390 * @see TextToSpeech#speak(String, int, HashMap) 391 * @see TextToSpeech#playEarcon(String, int, HashMap) 392 * @see TextToSpeech#synthesizeToFile(String, HashMap, String) 393 */ 394 public static final String KEY_PARAM_UTTERANCE_ID = "utteranceId"; 395 396 /** 397 * Parameter key to specify the speech volume relative to the current stream type 398 * volume used when speaking text. Volume is specified as a float ranging from 0 to 1 399 * where 0 is silence, and 1 is the maximum volume (the default behavior). 400 * 401 * @see TextToSpeech#speak(String, int, HashMap) 402 * @see TextToSpeech#playEarcon(String, int, HashMap) 403 */ 404 public static final String KEY_PARAM_VOLUME = "volume"; 405 406 /** 407 * Parameter key to specify how the speech is panned from left to right when speaking text. 408 * Pan is specified as a float ranging from -1 to +1 where -1 maps to a hard-left pan, 409 * 0 to center (the default behavior), and +1 to hard-right. 410 * 411 * @see TextToSpeech#speak(String, int, HashMap) 412 * @see TextToSpeech#playEarcon(String, int, HashMap) 413 */ 414 public static final String KEY_PARAM_PAN = "pan"; 415 416 } 417 418 private final Context mContext; 419 private Connection mServiceConnection; 420 private OnInitListener mInitListener; 421 private final Object mStartLock = new Object(); 422 423 private String mRequestedEngine; 424 private final Map<String, Uri> mEarcons; 425 private final Map<String, Uri> mUtterances; 426 private final Bundle mParams = new Bundle(); 427 428 /** 429 * The constructor for the TextToSpeech class, using the default TTS engine. 430 * This will also initialize the associated TextToSpeech engine if it isn't already running. 431 * 432 * @param context 433 * The context this instance is running in. 434 * @param listener 435 * The {@link TextToSpeech.OnInitListener} that will be called when the 436 * TextToSpeech engine has initialized. 437 */ 438 public TextToSpeech(Context context, OnInitListener listener) { 439 this(context, listener, null); 440 } 441 442 /** 443 * The constructor for the TextToSpeech class, using the given TTS engine. 444 * This will also initialize the associated TextToSpeech engine if it isn't already running. 445 * 446 * @param context 447 * The context this instance is running in. 448 * @param listener 449 * The {@link TextToSpeech.OnInitListener} that will be called when the 450 * TextToSpeech engine has initialized. 451 * @param engine Package name of the TTS engine to use. 452 */ 453 public TextToSpeech(Context context, OnInitListener listener, String engine) { 454 mContext = context; 455 mInitListener = listener; 456 mRequestedEngine = engine; 457 458 mEarcons = new HashMap<String, Uri>(); 459 mUtterances = new HashMap<String, Uri>(); 460 461 initTts(); 462 } 463 464 private String getPackageName() { 465 return mContext.getPackageName(); 466 } 467 468 private <R> R runActionNoReconnect(Action<R> action, R errorResult, String method) { 469 return runAction(action, errorResult, method, false); 470 } 471 472 private <R> R runAction(Action<R> action, R errorResult, String method) { 473 return runAction(action, errorResult, method, true); 474 } 475 476 private <R> R runAction(Action<R> action, R errorResult, String method, boolean reconnect) { 477 synchronized (mStartLock) { 478 if (mServiceConnection == null) { 479 Log.w(TAG, method + " failed: not bound to TTS engine"); 480 return errorResult; 481 } 482 return mServiceConnection.runAction(action, errorResult, method, reconnect); 483 } 484 } 485 486 private int initTts() { 487 String defaultEngine = getDefaultEngine(); 488 String engine = defaultEngine; 489 if (!areDefaultsEnforced() && !TextUtils.isEmpty(mRequestedEngine) 490 && isEngineEnabled(engine)) { 491 engine = mRequestedEngine; 492 } 493 494 // Try requested engine 495 if (connectToEngine(engine)) { 496 return SUCCESS; 497 } 498 499 // Fall back to user's default engine if different from the already tested one 500 if (!engine.equals(defaultEngine)) { 501 if (connectToEngine(defaultEngine)) { 502 return SUCCESS; 503 } 504 } 505 506 // Fall back to the hardcoded default if different from the two above 507 if (!defaultEngine.equals(Engine.DEFAULT_ENGINE) 508 && !engine.equals(Engine.DEFAULT_ENGINE)) { 509 if (connectToEngine(Engine.DEFAULT_ENGINE)) { 510 return SUCCESS; 511 } 512 } 513 514 return ERROR; 515 } 516 517 private boolean connectToEngine(String engine) { 518 Connection connection = new Connection(); 519 Intent intent = new Intent(Engine.INTENT_ACTION_TTS_SERVICE); 520 intent.setPackage(engine); 521 boolean bound = mContext.bindService(intent, connection, Context.BIND_AUTO_CREATE); 522 if (!bound) { 523 Log.e(TAG, "Failed to bind to " + engine); 524 dispatchOnInit(ERROR); 525 return false; 526 } else { 527 return true; 528 } 529 } 530 531 private void dispatchOnInit(int result) { 532 synchronized (mStartLock) { 533 if (mInitListener != null) { 534 mInitListener.onInit(result); 535 mInitListener = null; 536 } 537 } 538 } 539 540 /** 541 * Releases the resources used by the TextToSpeech engine. 542 * It is good practice for instance to call this method in the onDestroy() method of an Activity 543 * so the TextToSpeech engine can be cleanly stopped. 544 */ 545 public void shutdown() { 546 runActionNoReconnect(new Action<Void>() { 547 @Override 548 public Void run(ITextToSpeechService service) throws RemoteException { 549 service.setCallback(getPackageName(), null); 550 service.stop(getPackageName()); 551 mServiceConnection.disconnect(); 552 return null; 553 } 554 }, null, "shutdown"); 555 } 556 557 /** 558 * Adds a mapping between a string of text and a sound resource in a 559 * package. After a call to this method, subsequent calls to 560 * {@link #speak(String, int, HashMap)} will play the specified sound resource 561 * if it is available, or synthesize the text it is missing. 562 * 563 * @param text 564 * The string of text. Example: <code>"south_south_east"</code> 565 * 566 * @param packagename 567 * Pass the packagename of the application that contains the 568 * resource. If the resource is in your own application (this is 569 * the most common case), then put the packagename of your 570 * application here.<br/> 571 * Example: <b>"com.google.marvin.compass"</b><br/> 572 * The packagename can be found in the AndroidManifest.xml of 573 * your application. 574 * <p> 575 * <code><manifest xmlns:android="..." 576 * package="<b>com.google.marvin.compass</b>"></code> 577 * </p> 578 * 579 * @param resourceId 580 * Example: <code>R.raw.south_south_east</code> 581 * 582 * @return Code indicating success or failure. See {@link #ERROR} and {@link #SUCCESS}. 583 */ 584 public int addSpeech(String text, String packagename, int resourceId) { 585 synchronized (mStartLock) { 586 mUtterances.put(text, makeResourceUri(packagename, resourceId)); 587 return SUCCESS; 588 } 589 } 590 591 /** 592 * Adds a mapping between a string of text and a sound file. Using this, it 593 * is possible to add custom pronounciations for a string of text. 594 * After a call to this method, subsequent calls to {@link #speak(String, int, HashMap)} 595 * will play the specified sound resource if it is available, or synthesize the text it is 596 * missing. 597 * 598 * @param text 599 * The string of text. Example: <code>"south_south_east"</code> 600 * @param filename 601 * The full path to the sound file (for example: 602 * "/sdcard/mysounds/hello.wav") 603 * 604 * @return Code indicating success or failure. See {@link #ERROR} and {@link #SUCCESS}. 605 */ 606 public int addSpeech(String text, String filename) { 607 synchronized (mStartLock) { 608 mUtterances.put(text, Uri.parse(filename)); 609 return SUCCESS; 610 } 611 } 612 613 614 /** 615 * Adds a mapping between a string of text and a sound resource in a 616 * package. Use this to add custom earcons. 617 * 618 * @see #playEarcon(String, int, HashMap) 619 * 620 * @param earcon The name of the earcon. 621 * Example: <code>"[tick]"</code><br/> 622 * 623 * @param packagename 624 * the package name of the application that contains the 625 * resource. This can for instance be the package name of your own application. 626 * Example: <b>"com.google.marvin.compass"</b><br/> 627 * The package name can be found in the AndroidManifest.xml of 628 * the application containing the resource. 629 * <p> 630 * <code><manifest xmlns:android="..." 631 * package="<b>com.google.marvin.compass</b>"></code> 632 * </p> 633 * 634 * @param resourceId 635 * Example: <code>R.raw.tick_snd</code> 636 * 637 * @return Code indicating success or failure. See {@link #ERROR} and {@link #SUCCESS}. 638 */ 639 public int addEarcon(String earcon, String packagename, int resourceId) { 640 synchronized(mStartLock) { 641 mEarcons.put(earcon, makeResourceUri(packagename, resourceId)); 642 return SUCCESS; 643 } 644 } 645 646 /** 647 * Adds a mapping between a string of text and a sound file. 648 * Use this to add custom earcons. 649 * 650 * @see #playEarcon(String, int, HashMap) 651 * 652 * @param earcon 653 * The name of the earcon. 654 * Example: <code>"[tick]"</code> 655 * @param filename 656 * The full path to the sound file (for example: 657 * "/sdcard/mysounds/tick.wav") 658 * 659 * @return Code indicating success or failure. See {@link #ERROR} and {@link #SUCCESS}. 660 */ 661 public int addEarcon(String earcon, String filename) { 662 synchronized(mStartLock) { 663 mEarcons.put(earcon, Uri.parse(filename)); 664 return SUCCESS; 665 } 666 } 667 668 private Uri makeResourceUri(String packageName, int resourceId) { 669 return new Uri.Builder() 670 .scheme(ContentResolver.SCHEME_ANDROID_RESOURCE) 671 .encodedAuthority(packageName) 672 .appendEncodedPath(String.valueOf(resourceId)) 673 .build(); 674 } 675 676 /** 677 * Speaks the string using the specified queuing strategy and speech 678 * parameters. 679 * 680 * @param text The string of text to be spoken. 681 * @param queueMode The queuing strategy to use, {@link #QUEUE_ADD} or {@link #QUEUE_FLUSH}. 682 * @param params Parameters for the request. Can be null. 683 * Supported parameter names: 684 * {@link Engine#KEY_PARAM_STREAM}, 685 * {@link Engine#KEY_PARAM_UTTERANCE_ID}, 686 * {@link Engine#KEY_PARAM_VOLUME}, 687 * {@link Engine#KEY_PARAM_PAN}. 688 * 689 * @return {@link #ERROR} or {@link #SUCCESS}. 690 */ 691 public int speak(final String text, final int queueMode, final HashMap<String, String> params) { 692 return runAction(new Action<Integer>() { 693 @Override 694 public Integer run(ITextToSpeechService service) throws RemoteException { 695 Uri utteranceUri = mUtterances.get(text); 696 if (utteranceUri != null) { 697 return service.playAudio(getPackageName(), utteranceUri, queueMode, 698 getParams(params)); 699 } else { 700 return service.speak(getPackageName(), text, queueMode, getParams(params)); 701 } 702 } 703 }, ERROR, "speak"); 704 } 705 706 /** 707 * Plays the earcon using the specified queueing mode and parameters. 708 * The earcon must already have been added with {@link #addEarcon(String, String)} or 709 * {@link #addEarcon(String, String, int)}. 710 * 711 * @param earcon The earcon that should be played 712 * @param queueMode {@link #QUEUE_ADD} or {@link #QUEUE_FLUSH}. 713 * @param params Parameters for the request. Can be null. 714 * Supported parameter names: 715 * {@link Engine#KEY_PARAM_STREAM}, 716 * {@link Engine#KEY_PARAM_UTTERANCE_ID}. 717 * 718 * @return {@link #ERROR} or {@link #SUCCESS}. 719 */ 720 public int playEarcon(final String earcon, final int queueMode, 721 final HashMap<String, String> params) { 722 return runAction(new Action<Integer>() { 723 @Override 724 public Integer run(ITextToSpeechService service) throws RemoteException { 725 Uri earconUri = mEarcons.get(earcon); 726 if (earconUri == null) { 727 return ERROR; 728 } 729 return service.playAudio(getPackageName(), earconUri, queueMode, 730 getParams(params)); 731 } 732 }, ERROR, "playEarcon"); 733 } 734 735 /** 736 * Plays silence for the specified amount of time using the specified 737 * queue mode. 738 * 739 * @param durationInMs The duration of the silence. 740 * @param queueMode {@link #QUEUE_ADD} or {@link #QUEUE_FLUSH}. 741 * @param params Parameters for the request. Can be null. 742 * Supported parameter names: 743 * {@link Engine#KEY_PARAM_UTTERANCE_ID}. 744 * 745 * @return {@link #ERROR} or {@link #SUCCESS}. 746 */ 747 public int playSilence(final long durationInMs, final int queueMode, 748 final HashMap<String, String> params) { 749 return runAction(new Action<Integer>() { 750 @Override 751 public Integer run(ITextToSpeechService service) throws RemoteException { 752 return service.playSilence(getPackageName(), durationInMs, queueMode, 753 getParams(params)); 754 } 755 }, ERROR, "playSilence"); 756 } 757 758 /** 759 * Checks whether the TTS engine is busy speaking. 760 * 761 * @return {@code true} if the TTS engine is speaking. 762 */ 763 public boolean isSpeaking() { 764 return runAction(new Action<Boolean>() { 765 @Override 766 public Boolean run(ITextToSpeechService service) throws RemoteException { 767 return service.isSpeaking(); 768 } 769 }, false, "isSpeaking"); 770 } 771 772 /** 773 * Interrupts the current utterance (whether played or rendered to file) and discards other 774 * utterances in the queue. 775 * 776 * @return {@link #ERROR} or {@link #SUCCESS}. 777 */ 778 public int stop() { 779 return runAction(new Action<Integer>() { 780 @Override 781 public Integer run(ITextToSpeechService service) throws RemoteException { 782 return service.stop(getPackageName()); 783 } 784 }, ERROR, "stop"); 785 } 786 787 /** 788 * Sets the speech rate. 789 * 790 * This has no effect on any pre-recorded speech. 791 * 792 * @param speechRate Speech rate. {@code 1.0} is the normal speech rate, 793 * lower values slow down the speech ({@code 0.5} is half the normal speech rate), 794 * greater values accelerate it ({@code 2.0} is twice the normal speech rate). 795 * 796 * @return {@link #ERROR} or {@link #SUCCESS}. 797 */ 798 public int setSpeechRate(float speechRate) { 799 if (speechRate > 0.0f) { 800 int intRate = (int)(speechRate * 100); 801 if (intRate > 0) { 802 synchronized (mStartLock) { 803 mParams.putInt(Engine.KEY_PARAM_RATE, intRate); 804 } 805 return SUCCESS; 806 } 807 } 808 return ERROR; 809 } 810 811 /** 812 * Sets the speech pitch for the TextToSpeech engine. 813 * 814 * This has no effect on any pre-recorded speech. 815 * 816 * @param pitch Speech pitch. {@code 1.0} is the normal pitch, 817 * lower values lower the tone of the synthesized voice, 818 * greater values increase it. 819 * 820 * @return {@link #ERROR} or {@link #SUCCESS}. 821 */ 822 public int setPitch(float pitch) { 823 if (pitch > 0.0f) { 824 int intPitch = (int)(pitch * 100); 825 if (intPitch > 0) { 826 synchronized (mStartLock) { 827 mParams.putInt(Engine.KEY_PARAM_PITCH, intPitch); 828 } 829 return SUCCESS; 830 } 831 } 832 return ERROR; 833 } 834 835 /** 836 * Sets the text-to-speech language. 837 * The TTS engine will try to use the closest match to the specified 838 * language as represented by the Locale, but there is no guarantee that the exact same Locale 839 * will be used. Use {@link #isLanguageAvailable(Locale)} to check the level of support 840 * before choosing the language to use for the next utterances. 841 * 842 * @param loc The locale describing the language to be used. 843 * 844 * @return Code indicating the support status for the locale. See {@link #LANG_AVAILABLE}, 845 * {@link #LANG_COUNTRY_AVAILABLE}, {@link #LANG_COUNTRY_VAR_AVAILABLE}, 846 * {@link #LANG_MISSING_DATA} and {@link #LANG_NOT_SUPPORTED}. 847 */ 848 public int setLanguage(final Locale loc) { 849 return runAction(new Action<Integer>() { 850 @Override 851 public Integer run(ITextToSpeechService service) throws RemoteException { 852 if (loc == null) { 853 return LANG_NOT_SUPPORTED; 854 } 855 String language = loc.getISO3Language(); 856 String country = loc.getISO3Country(); 857 String variant = loc.getVariant(); 858 // Check if the language, country, variant are available, and cache 859 // the available parts. 860 // Note that the language is not actually set here, instead it is cached so it 861 // will be associated with all upcoming utterances. 862 int result = service.loadLanguage(language, country, variant); 863 if (result >= LANG_AVAILABLE){ 864 if (result < LANG_COUNTRY_VAR_AVAILABLE) { 865 variant = ""; 866 if (result < LANG_COUNTRY_AVAILABLE) { 867 country = ""; 868 } 869 } 870 mParams.putString(Engine.KEY_PARAM_LANGUAGE, language); 871 mParams.putString(Engine.KEY_PARAM_COUNTRY, country); 872 mParams.putString(Engine.KEY_PARAM_VARIANT, variant); 873 } 874 return result; 875 } 876 }, LANG_NOT_SUPPORTED, "setLanguage"); 877 } 878 879 /** 880 * Returns a Locale instance describing the language currently being used by the TextToSpeech 881 * engine. 882 * 883 * @return language, country (if any) and variant (if any) used by the engine stored in a Locale 884 * instance, or {@code null} on error. 885 */ 886 public Locale getLanguage() { 887 return runAction(new Action<Locale>() { 888 @Override 889 public Locale run(ITextToSpeechService service) throws RemoteException { 890 String[] locStrings = service.getLanguage(); 891 if (locStrings != null && locStrings.length == 3) { 892 return new Locale(locStrings[0], locStrings[1], locStrings[2]); 893 } 894 return null; 895 } 896 }, null, "getLanguage"); 897 } 898 899 /** 900 * Checks if the specified language as represented by the Locale is available and supported. 901 * 902 * @param loc The Locale describing the language to be used. 903 * 904 * @return Code indicating the support status for the locale. See {@link #LANG_AVAILABLE}, 905 * {@link #LANG_COUNTRY_AVAILABLE}, {@link #LANG_COUNTRY_VAR_AVAILABLE}, 906 * {@link #LANG_MISSING_DATA} and {@link #LANG_NOT_SUPPORTED}. 907 */ 908 public int isLanguageAvailable(final Locale loc) { 909 return runAction(new Action<Integer>() { 910 @Override 911 public Integer run(ITextToSpeechService service) throws RemoteException { 912 return service.isLanguageAvailable(loc.getISO3Language(), 913 loc.getISO3Country(), loc.getVariant()); 914 } 915 }, LANG_NOT_SUPPORTED, "isLanguageAvailable"); 916 } 917 918 /** 919 * Synthesizes the given text to a file using the specified parameters. 920 * 921 * @param text Thetext that should be synthesized 922 * @param params Parameters for the request. Can be null. 923 * Supported parameter names: 924 * {@link Engine#KEY_PARAM_UTTERANCE_ID}. 925 * @param filename Absolute file filename to write the generated audio data to.It should be 926 * something like "/sdcard/myappsounds/mysound.wav". 927 * 928 * @return {@link #ERROR} or {@link #SUCCESS}. 929 */ 930 public int synthesizeToFile(final String text, final HashMap<String, String> params, 931 final String filename) { 932 return runAction(new Action<Integer>() { 933 @Override 934 public Integer run(ITextToSpeechService service) throws RemoteException { 935 return service.synthesizeToFile(getPackageName(), text, filename, 936 getParams(params)); 937 } 938 }, ERROR, "synthesizeToFile"); 939 } 940 941 private Bundle getParams(HashMap<String, String> params) { 942 if (params != null && !params.isEmpty()) { 943 Bundle bundle = new Bundle(mParams); 944 copyIntParam(bundle, params, Engine.KEY_PARAM_STREAM); 945 copyStringParam(bundle, params, Engine.KEY_PARAM_UTTERANCE_ID); 946 copyFloatParam(bundle, params, Engine.KEY_PARAM_VOLUME); 947 copyFloatParam(bundle, params, Engine.KEY_PARAM_PAN); 948 return bundle; 949 } else { 950 return mParams; 951 } 952 } 953 954 private void copyStringParam(Bundle bundle, HashMap<String, String> params, String key) { 955 String value = params.get(key); 956 if (value != null) { 957 bundle.putString(key, value); 958 } 959 } 960 961 private void copyIntParam(Bundle bundle, HashMap<String, String> params, String key) { 962 String valueString = params.get(key); 963 if (!TextUtils.isEmpty(valueString)) { 964 try { 965 int value = Integer.parseInt(valueString); 966 bundle.putInt(key, value); 967 } catch (NumberFormatException ex) { 968 // don't set the value in the bundle 969 } 970 } 971 } 972 973 private void copyFloatParam(Bundle bundle, HashMap<String, String> params, String key) { 974 String valueString = params.get(key); 975 if (!TextUtils.isEmpty(valueString)) { 976 try { 977 float value = Float.parseFloat(valueString); 978 bundle.putFloat(key, value); 979 } catch (NumberFormatException ex) { 980 // don't set the value in the bundle 981 } 982 } 983 } 984 985 /** 986 * Sets the listener that will be notified when synthesis of an utterance completes. 987 * 988 * @param listener The listener to use. 989 * 990 * @return {@link #ERROR} or {@link #SUCCESS}. 991 */ 992 public int setOnUtteranceCompletedListener(final OnUtteranceCompletedListener listener) { 993 return runAction(new Action<Integer>() { 994 @Override 995 public Integer run(ITextToSpeechService service) throws RemoteException { 996 ITextToSpeechCallback.Stub callback = new ITextToSpeechCallback.Stub() { 997 public void utteranceCompleted(String utteranceId) { 998 if (listener != null) { 999 listener.onUtteranceCompleted(utteranceId); 1000 } 1001 } 1002 }; 1003 service.setCallback(getPackageName(), callback); 1004 return SUCCESS; 1005 } 1006 }, ERROR, "setOnUtteranceCompletedListener"); 1007 } 1008 1009 /** 1010 * Sets the TTS engine to use. 1011 * 1012 * @param enginePackageName The package name for the synthesis engine (e.g. "com.svox.pico") 1013 * 1014 * @return {@link #ERROR} or {@link #SUCCESS}. 1015 */ 1016 // TODO: add @Deprecated{This method does not tell the caller when the new engine 1017 // has been initialized. You should create a new TextToSpeech object with the new 1018 // engine instead.} 1019 public int setEngineByPackageName(String enginePackageName) { 1020 mRequestedEngine = enginePackageName; 1021 return initTts(); 1022 } 1023 1024 /** 1025 * Gets the package name of the default speech synthesis engine. 1026 * 1027 * @return Package name of the TTS engine that the user has chosen as their default. 1028 */ 1029 public String getDefaultEngine() { 1030 String engine = Settings.Secure.getString(mContext.getContentResolver(), 1031 Settings.Secure.TTS_DEFAULT_SYNTH); 1032 return engine != null ? engine : Engine.DEFAULT_ENGINE; 1033 } 1034 1035 /** 1036 * Checks whether the user's settings should override settings requested by the calling 1037 * application. 1038 */ 1039 public boolean areDefaultsEnforced() { 1040 return Settings.Secure.getInt(mContext.getContentResolver(), 1041 Settings.Secure.TTS_USE_DEFAULTS, Engine.USE_DEFAULTS) == 1; 1042 } 1043 1044 private boolean isEngineEnabled(String engine) { 1045 if (Engine.DEFAULT_ENGINE.equals(engine)) { 1046 return true; 1047 } 1048 for (String enabled : getEnabledEngines()) { 1049 if (engine.equals(enabled)) { 1050 return true; 1051 } 1052 } 1053 return false; 1054 } 1055 1056 private String[] getEnabledEngines() { 1057 String str = Settings.Secure.getString(mContext.getContentResolver(), 1058 Settings.Secure.TTS_ENABLED_PLUGINS); 1059 if (TextUtils.isEmpty(str)) { 1060 return new String[0]; 1061 } 1062 return str.split(" "); 1063 } 1064 1065 /** 1066 * Gets a list of all installed TTS engines. 1067 * 1068 * @return A list of engine info objects. The list can be empty, but will never by {@code null}. 1069 */ 1070 public List<EngineInfo> getEngines() { 1071 PackageManager pm = mContext.getPackageManager(); 1072 Intent intent = new Intent(Engine.INTENT_ACTION_TTS_SERVICE); 1073 List<ResolveInfo> resolveInfos = 1074 pm.queryIntentServices(intent, PackageManager.MATCH_DEFAULT_ONLY); 1075 if (resolveInfos == null) return Collections.emptyList(); 1076 List<EngineInfo> engines = new ArrayList<EngineInfo>(resolveInfos.size()); 1077 for (ResolveInfo resolveInfo : resolveInfos) { 1078 ServiceInfo service = resolveInfo.serviceInfo; 1079 if (service != null) { 1080 EngineInfo engine = new EngineInfo(); 1081 // Using just the package name isn't great, since it disallows having 1082 // multiple engines in the same package, but that's what the existing API does. 1083 engine.name = service.packageName; 1084 CharSequence label = service.loadLabel(pm); 1085 engine.label = TextUtils.isEmpty(label) ? engine.name : label.toString(); 1086 engine.icon = service.getIconResource(); 1087 engines.add(engine); 1088 } 1089 } 1090 return engines; 1091 } 1092 1093 private class Connection implements ServiceConnection { 1094 private ITextToSpeechService mService; 1095 1096 public void onServiceConnected(ComponentName name, IBinder service) { 1097 Log.i(TAG, "Connected to " + name); 1098 synchronized(mStartLock) { 1099 if (mServiceConnection != null) { 1100 // Disconnect any previous service connection 1101 mServiceConnection.disconnect(); 1102 } 1103 mServiceConnection = this; 1104 mService = ITextToSpeechService.Stub.asInterface(service); 1105 dispatchOnInit(SUCCESS); 1106 } 1107 } 1108 1109 public void onServiceDisconnected(ComponentName name) { 1110 synchronized(mStartLock) { 1111 mService = null; 1112 // If this is the active connection, clear it 1113 if (mServiceConnection == this) { 1114 mServiceConnection = null; 1115 } 1116 } 1117 } 1118 1119 public void disconnect() { 1120 mContext.unbindService(this); 1121 } 1122 1123 public <R> R runAction(Action<R> action, R errorResult, String method, boolean reconnect) { 1124 try { 1125 synchronized (mStartLock) { 1126 if (mService == null) { 1127 Log.w(TAG, method + " failed: not connected to TTS engine"); 1128 return errorResult; 1129 } 1130 return action.run(mService); 1131 } 1132 } catch (RemoteException ex) { 1133 Log.e(TAG, method + " failed", ex); 1134 if (reconnect) { 1135 disconnect(); 1136 initTts(); 1137 } 1138 return errorResult; 1139 } 1140 } 1141 } 1142 1143 private interface Action<R> { 1144 R run(ITextToSpeechService service) throws RemoteException; 1145 } 1146 1147 /** 1148 * Information about an installed text-to-speech engine. 1149 * 1150 * @see TextToSpeech#getEngines 1151 */ 1152 public static class EngineInfo { 1153 /** 1154 * Engine package name.. 1155 */ 1156 public String name; 1157 /** 1158 * Localized label for the engine. 1159 */ 1160 public String label; 1161 /** 1162 * Icon for the engine. 1163 */ 1164 public int icon; 1165 1166 @Override 1167 public String toString() { 1168 return "EngineInfo{name=" + name + "}"; 1169 } 1170 1171 } 1172} 1173