TextToSpeechService.java revision 067a21b2469a689dc075e8897756f2b3acb4495a
1/* 2 * Copyright (C) 2011 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.app.Service; 19import android.content.Intent; 20import android.net.Uri; 21import android.os.Binder; 22import android.os.Bundle; 23import android.os.Handler; 24import android.os.HandlerThread; 25import android.os.IBinder; 26import android.os.Looper; 27import android.os.Message; 28import android.os.MessageQueue; 29import android.os.ParcelFileDescriptor; 30import android.os.RemoteCallbackList; 31import android.os.RemoteException; 32import android.provider.Settings; 33import android.speech.tts.TextToSpeech.Engine; 34import android.text.TextUtils; 35import android.util.Log; 36 37import java.io.FileOutputStream; 38import java.io.IOException; 39import java.util.ArrayList; 40import java.util.HashMap; 41import java.util.List; 42import java.util.Locale; 43import java.util.Map; 44import java.util.MissingResourceException; 45import java.util.Set; 46 47 48/** 49 * Abstract base class for TTS engine implementations. The following methods 50 * need to be implemented for V1 API ({@link TextToSpeech}) implementation. 51 * <ul> 52 * <li>{@link #onIsLanguageAvailable}</li> 53 * <li>{@link #onLoadLanguage}</li> 54 * <li>{@link #onGetLanguage}</li> 55 * <li>{@link #onSynthesizeText}</li> 56 * <li>{@link #onStop}</li> 57 * </ul> 58 * The first three deal primarily with language management, and are used to 59 * query the engine for it's support for a given language and indicate to it 60 * that requests in a given language are imminent. 61 * 62 * {@link #onSynthesizeText} is central to the engine implementation. The 63 * implementation should synthesize text as per the request parameters and 64 * return synthesized data via the supplied callback. This class and its helpers 65 * will then consume that data, which might mean queuing it for playback or writing 66 * it to a file or similar. All calls to this method will be on a single thread, 67 * which will be different from the main thread of the service. Synthesis must be 68 * synchronous which means the engine must NOT hold on to the callback or call any 69 * methods on it after the method returns. 70 * 71 * {@link #onStop} tells the engine that it should stop 72 * all ongoing synthesis, if any. Any pending data from the current synthesis 73 * will be discarded. 74 * 75 * {@link #onGetLanguage} is not required as of JELLYBEAN_MR2 (API 18) and later, it is only 76 * called on earlier versions of Android. 77 * <p> 78 * In order to fully support the V2 API ({@link TextToSpeechClient}), 79 * these methods must be implemented: 80 * <ul> 81 * <li>{@link #onSynthesizeTextV2}</li> 82 * <li>{@link #checkVoicesInfo}</li> 83 * <li>{@link #onVoicesInfoChange}</li> 84 * <li>{@link #implementsV2API}</li> 85 * </ul> 86 * In addition {@link #implementsV2API} has to return true. 87 * <p> 88 * If the service does not implement these methods and {@link #implementsV2API} returns false, 89 * then the V2 API will be provided by converting V2 requests ({@link #onSynthesizeTextV2}) 90 * to V1 requests ({@link #onSynthesizeText}). On service setup, all of the available device 91 * locales will be fed to {@link #onIsLanguageAvailable} to check if they are supported. 92 * If they are, embedded and/or network voices will be created depending on the result of 93 * {@link #onGetFeaturesForLanguage}. 94 * <p> 95 * Note that a V2 service will still receive requests from V1 clients and has to implement all 96 * of the V1 API methods. 97 */ 98public abstract class TextToSpeechService extends Service { 99 100 private static final boolean DBG = false; 101 private static final String TAG = "TextToSpeechService"; 102 103 private static final String SYNTH_THREAD_NAME = "SynthThread"; 104 105 private SynthHandler mSynthHandler; 106 // A thread and it's associated handler for playing back any audio 107 // associated with this TTS engine. Will handle all requests except synthesis 108 // to file requests, which occur on the synthesis thread. 109 private AudioPlaybackHandler mAudioPlaybackHandler; 110 private TtsEngines mEngineHelper; 111 112 private CallbackMap mCallbacks; 113 private String mPackageName; 114 115 private final Object mVoicesInfoLock = new Object(); 116 117 private List<VoiceInfo> mVoicesInfoList; 118 private Map<String, VoiceInfo> mVoicesInfoLookup; 119 120 @Override 121 public void onCreate() { 122 if (DBG) Log.d(TAG, "onCreate()"); 123 super.onCreate(); 124 125 SynthThread synthThread = new SynthThread(); 126 synthThread.start(); 127 mSynthHandler = new SynthHandler(synthThread.getLooper()); 128 129 mAudioPlaybackHandler = new AudioPlaybackHandler(); 130 mAudioPlaybackHandler.start(); 131 132 mEngineHelper = new TtsEngines(this); 133 134 mCallbacks = new CallbackMap(); 135 136 mPackageName = getApplicationInfo().packageName; 137 138 String[] defaultLocale = getSettingsLocale(); 139 140 // Load default language 141 onLoadLanguage(defaultLocale[0], defaultLocale[1], defaultLocale[2]); 142 } 143 144 @Override 145 public void onDestroy() { 146 if (DBG) Log.d(TAG, "onDestroy()"); 147 148 // Tell the synthesizer to stop 149 mSynthHandler.quit(); 150 // Tell the audio playback thread to stop. 151 mAudioPlaybackHandler.quit(); 152 // Unregister all callbacks. 153 mCallbacks.kill(); 154 155 super.onDestroy(); 156 } 157 158 /** 159 * Checks whether the engine supports a given language. 160 * 161 * Can be called on multiple threads. 162 * 163 * Its return values HAVE to be consistent with onLoadLanguage. 164 * 165 * @param lang ISO-3 language code. 166 * @param country ISO-3 country code. May be empty or null. 167 * @param variant Language variant. May be empty or null. 168 * @return Code indicating the support status for the locale. 169 * One of {@link TextToSpeech#LANG_AVAILABLE}, 170 * {@link TextToSpeech#LANG_COUNTRY_AVAILABLE}, 171 * {@link TextToSpeech#LANG_COUNTRY_VAR_AVAILABLE}, 172 * {@link TextToSpeech#LANG_MISSING_DATA} 173 * {@link TextToSpeech#LANG_NOT_SUPPORTED}. 174 */ 175 protected abstract int onIsLanguageAvailable(String lang, String country, String variant); 176 177 /** 178 * Returns the language, country and variant currently being used by the TTS engine. 179 * 180 * This method will be called only on Android 4.2 and before (API <= 17). In later versions 181 * this method is not called by the Android TTS framework. 182 * 183 * Can be called on multiple threads. 184 * 185 * @return A 3-element array, containing language (ISO 3-letter code), 186 * country (ISO 3-letter code) and variant used by the engine. 187 * The country and variant may be {@code ""}. If country is empty, then variant must 188 * be empty too. 189 * @see Locale#getISO3Language() 190 * @see Locale#getISO3Country() 191 * @see Locale#getVariant() 192 */ 193 protected abstract String[] onGetLanguage(); 194 195 /** 196 * Notifies the engine that it should load a speech synthesis language. There is no guarantee 197 * that this method is always called before the language is used for synthesis. It is merely 198 * a hint to the engine that it will probably get some synthesis requests for this language 199 * at some point in the future. 200 * 201 * Can be called on multiple threads. 202 * In <= Android 4.2 (<= API 17) can be called on main and service binder threads. 203 * In > Android 4.2 (> API 17) can be called on main and synthesis threads. 204 * 205 * @param lang ISO-3 language code. 206 * @param country ISO-3 country code. May be empty or null. 207 * @param variant Language variant. May be empty or null. 208 * @return Code indicating the support status for the locale. 209 * One of {@link TextToSpeech#LANG_AVAILABLE}, 210 * {@link TextToSpeech#LANG_COUNTRY_AVAILABLE}, 211 * {@link TextToSpeech#LANG_COUNTRY_VAR_AVAILABLE}, 212 * {@link TextToSpeech#LANG_MISSING_DATA} 213 * {@link TextToSpeech#LANG_NOT_SUPPORTED}. 214 */ 215 protected abstract int onLoadLanguage(String lang, String country, String variant); 216 217 /** 218 * Notifies the service that it should stop any in-progress speech synthesis. 219 * This method can be called even if no speech synthesis is currently in progress. 220 * 221 * Can be called on multiple threads, but not on the synthesis thread. 222 */ 223 protected abstract void onStop(); 224 225 /** 226 * Tells the service to synthesize speech from the given text. This method 227 * should block until the synthesis is finished. Used for requests from V1 228 * clients ({@link android.speech.tts.TextToSpeech}). Called on the synthesis 229 * thread. 230 * 231 * @param request The synthesis request. 232 * @param callback The callback that the engine must use to make data 233 * available for playback or for writing to a file. 234 */ 235 protected abstract void onSynthesizeText(SynthesisRequest request, 236 SynthesisCallback callback); 237 238 /** 239 * Check the available voices data and return an immutable list of the available voices. 240 * The output of this method will be passed to clients to allow them to configure synthesis 241 * requests. 242 * 243 * Can be called on multiple threads. 244 * 245 * The result of this method will be saved and served to all TTS clients. If a TTS service wants 246 * to update the set of available voices, it should call the {@link #forceVoicesInfoCheck()} 247 * method. 248 */ 249 protected List<VoiceInfo> checkVoicesInfo() { 250 if (implementsV2API()) { 251 throw new IllegalStateException("For proper V2 API implementation this method has to" + 252 " be implemented"); 253 } 254 255 // V2 to V1 interface adapter. This allows using V2 client interface on V1-only services. 256 Bundle defaultParams = new Bundle(); 257 defaultParams.putFloat(TextToSpeechClient.Params.SPEECH_PITCH, 1.0f); 258 // Speech speed <= 0 makes it use a system wide setting 259 defaultParams.putFloat(TextToSpeechClient.Params.SPEECH_SPEED, 0.0f); 260 261 // Enumerate all locales and check if they are available 262 ArrayList<VoiceInfo> voicesInfo = new ArrayList<VoiceInfo>(); 263 int id = 0; 264 for (Locale locale : Locale.getAvailableLocales()) { 265 int expectedStatus = TextToSpeech.LANG_COUNTRY_VAR_AVAILABLE; 266 if (locale.getVariant().isEmpty()) { 267 if (locale.getCountry().isEmpty()) { 268 expectedStatus = TextToSpeech.LANG_AVAILABLE; 269 } else { 270 expectedStatus = TextToSpeech.LANG_COUNTRY_AVAILABLE; 271 } 272 } 273 try { 274 int localeStatus = onIsLanguageAvailable(locale.getISO3Language(), 275 locale.getISO3Country(), locale.getVariant()); 276 if (localeStatus != expectedStatus) { 277 continue; 278 } 279 } catch (MissingResourceException e) { 280 // Ignore locale without iso 3 codes 281 continue; 282 } 283 284 Set<String> features = onGetFeaturesForLanguage(locale.getISO3Language(), 285 locale.getISO3Country(), locale.getVariant()); 286 287 VoiceInfo.Builder builder = new VoiceInfo.Builder(); 288 builder.setLatency(VoiceInfo.LATENCY_NORMAL); 289 builder.setQuality(VoiceInfo.QUALITY_NORMAL); 290 builder.setLocale(locale); 291 builder.setParamsWithDefaults(defaultParams); 292 293 if (features == null || features.contains( 294 TextToSpeech.Engine.KEY_FEATURE_EMBEDDED_SYNTHESIS)) { 295 builder.setName(locale.toString() + "-embedded"); 296 builder.setRequiresNetworkConnection(false); 297 voicesInfo.add(builder.build()); 298 } 299 300 if (features != null && features.contains( 301 TextToSpeech.Engine.KEY_FEATURE_NETWORK_SYNTHESIS)) { 302 builder.setName(locale.toString() + "-network"); 303 builder.setRequiresNetworkConnection(true); 304 voicesInfo.add(builder.build()); 305 } 306 } 307 308 return voicesInfo; 309 } 310 311 /** 312 * Tells the synthesis thread that it should reload voice data. 313 * There's a high probability that the underlying set of available voice data has changed. 314 * Called only on the synthesis thread. 315 */ 316 protected void onVoicesInfoChange() { 317 318 } 319 320 /** 321 * Tells the service to synthesize speech from the given text. This method 322 * should block until the synthesis is finished. Used for requests from V2 323 * client {@link android.speech.tts.TextToSpeechClient}. Called on the 324 * synthesis thread. 325 * 326 * @param request The synthesis request. 327 * @param callback The callback the the engine must use to make data 328 * available for playback or for writing to a file. 329 */ 330 protected void onSynthesizeTextV2(SynthesisRequestV2 request, 331 VoiceInfo selectedVoice, 332 SynthesisCallback callback) { 333 if (implementsV2API()) { 334 throw new IllegalStateException("For proper V2 API implementation this method has to" + 335 " be implemented"); 336 } 337 338 // Convert to V1 params 339 int speechRate = (int) (request.getVoiceParams().getFloat( 340 TextToSpeechClient.Params.SPEECH_SPEED, 1.0f) * 100); 341 int speechPitch = (int) (request.getVoiceParams().getFloat( 342 TextToSpeechClient.Params.SPEECH_PITCH, 1.0f) * 100); 343 344 // Provide adapter to V1 API 345 Bundle params = new Bundle(); 346 params.putString(TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID, request.getUtteranceId()); 347 params.putInt(TextToSpeech.Engine.KEY_PARAM_PITCH, speechPitch); 348 params.putInt(TextToSpeech.Engine.KEY_PARAM_RATE, speechRate); 349 if (selectedVoice.getRequiresNetworkConnection()) { 350 params.putString(TextToSpeech.Engine.KEY_FEATURE_NETWORK_SYNTHESIS, "true"); 351 } else { 352 params.putString(TextToSpeech.Engine.KEY_FEATURE_EMBEDDED_SYNTHESIS, "true"); 353 } 354 355 // Build V1 request 356 SynthesisRequest requestV1 = new SynthesisRequest(request.getText(), params); 357 Locale locale = selectedVoice.getLocale(); 358 requestV1.setLanguage(locale.getISO3Language(), locale.getISO3Country(), 359 locale.getVariant()); 360 requestV1.setSpeechRate(speechRate); 361 requestV1.setPitch(speechPitch); 362 363 // Synthesize using V1 interface 364 onSynthesizeText(requestV1, callback); 365 } 366 367 /** 368 * If true, this service implements proper V2 TTS API service. If it's false, 369 * V2 API will be provided through adapter. 370 */ 371 protected boolean implementsV2API() { 372 return false; 373 } 374 375 /** 376 * Queries the service for a set of features supported for a given language. 377 * 378 * Can be called on multiple threads. 379 * 380 * @param lang ISO-3 language code. 381 * @param country ISO-3 country code. May be empty or null. 382 * @param variant Language variant. May be empty or null. 383 * @return A list of features supported for the given language. 384 */ 385 protected Set<String> onGetFeaturesForLanguage(String lang, String country, String variant) { 386 return null; 387 } 388 389 private List<VoiceInfo> getVoicesInfo() { 390 synchronized (mVoicesInfoLock) { 391 if (mVoicesInfoList == null) { 392 // Get voices. Defensive copy to make sure TTS engine won't alter the list. 393 mVoicesInfoList = new ArrayList<VoiceInfo>(checkVoicesInfo()); 394 // Build lookup map 395 mVoicesInfoLookup = new HashMap<String, VoiceInfo>((int) ( 396 mVoicesInfoList.size()*1.5f)); 397 for (VoiceInfo voiceInfo : mVoicesInfoList) { 398 VoiceInfo prev = mVoicesInfoLookup.put(voiceInfo.getName(), voiceInfo); 399 if (prev != null) { 400 Log.e(TAG, "Duplicate name (" + voiceInfo.getName() + ") of the voice "); 401 } 402 } 403 } 404 return mVoicesInfoList; 405 } 406 } 407 408 public VoiceInfo getVoicesInfoWithName(String name) { 409 synchronized (mVoicesInfoLock) { 410 if (mVoicesInfoLookup != null) { 411 return mVoicesInfoLookup.get(name); 412 } 413 } 414 return null; 415 } 416 417 /** 418 * Force TTS service to reevaluate the set of available languages. Will result in 419 * a call to {@link #checkVoicesInfo()} on the same thread, {@link #onVoicesInfoChange} 420 * on the synthesizer thread and callback to 421 * {@link TextToSpeechClient.ConnectionCallbacks#onEngineStatusChange} of all connected 422 * TTS clients. 423 * 424 * Use this method only if you know that set of available languages changed. 425 * 426 * Can be called on multiple threads. 427 */ 428 public void forceVoicesInfoCheck() { 429 synchronized (mVoicesInfoLock) { 430 List<VoiceInfo> old = mVoicesInfoList; 431 432 mVoicesInfoList = null; // Force recreation of voices info list 433 getVoicesInfo(); 434 435 if (mVoicesInfoList == null) { 436 throw new IllegalStateException("This method applies only to services " + 437 "supporting V2 TTS API. This services doesn't support V2 TTS API."); 438 } 439 440 if (old != null) { 441 // Flush all existing items, and inform synthesis thread about the change. 442 mSynthHandler.enqueueSpeechItem(TextToSpeech.QUEUE_FLUSH, 443 new VoicesInfoChangeItem()); 444 // TODO: Handle items that may be added to queue after SynthesizerRestartItem 445 // but before client reconnection 446 // Disconnect all of them 447 mCallbacks.dispatchVoicesInfoChange(mVoicesInfoList); 448 } 449 } 450 } 451 452 private int getDefaultSpeechRate() { 453 return getSecureSettingInt(Settings.Secure.TTS_DEFAULT_RATE, Engine.DEFAULT_RATE); 454 } 455 456 private String[] getSettingsLocale() { 457 final String locale = mEngineHelper.getLocalePrefForEngine(mPackageName); 458 return TtsEngines.parseLocalePref(locale); 459 } 460 461 private int getSecureSettingInt(String name, int defaultValue) { 462 return Settings.Secure.getInt(getContentResolver(), name, defaultValue); 463 } 464 465 /** 466 * Synthesizer thread. This thread is used to run {@link SynthHandler}. 467 */ 468 private class SynthThread extends HandlerThread implements MessageQueue.IdleHandler { 469 470 private boolean mFirstIdle = true; 471 472 public SynthThread() { 473 super(SYNTH_THREAD_NAME, android.os.Process.THREAD_PRIORITY_DEFAULT); 474 } 475 476 @Override 477 protected void onLooperPrepared() { 478 getLooper().getQueue().addIdleHandler(this); 479 } 480 481 @Override 482 public boolean queueIdle() { 483 if (mFirstIdle) { 484 mFirstIdle = false; 485 } else { 486 broadcastTtsQueueProcessingCompleted(); 487 } 488 return true; 489 } 490 491 private void broadcastTtsQueueProcessingCompleted() { 492 Intent i = new Intent(TextToSpeech.ACTION_TTS_QUEUE_PROCESSING_COMPLETED); 493 if (DBG) Log.d(TAG, "Broadcasting: " + i); 494 sendBroadcast(i); 495 } 496 } 497 498 private class SynthHandler extends Handler { 499 private SpeechItem mCurrentSpeechItem = null; 500 501 public SynthHandler(Looper looper) { 502 super(looper); 503 } 504 505 private synchronized SpeechItem getCurrentSpeechItem() { 506 return mCurrentSpeechItem; 507 } 508 509 private synchronized SpeechItem setCurrentSpeechItem(SpeechItem speechItem) { 510 SpeechItem old = mCurrentSpeechItem; 511 mCurrentSpeechItem = speechItem; 512 return old; 513 } 514 515 private synchronized SpeechItem maybeRemoveCurrentSpeechItem(Object callerIdentity) { 516 if (mCurrentSpeechItem != null && 517 (mCurrentSpeechItem.getCallerIdentity() == callerIdentity)) { 518 SpeechItem current = mCurrentSpeechItem; 519 mCurrentSpeechItem = null; 520 return current; 521 } 522 523 return null; 524 } 525 526 public boolean isSpeaking() { 527 return getCurrentSpeechItem() != null; 528 } 529 530 public void quit() { 531 // Don't process any more speech items 532 getLooper().quit(); 533 // Stop the current speech item 534 SpeechItem current = setCurrentSpeechItem(null); 535 if (current != null) { 536 current.stop(); 537 } 538 // The AudioPlaybackHandler will be destroyed by the caller. 539 } 540 541 /** 542 * Adds a speech item to the queue. 543 * 544 * Called on a service binder thread. 545 */ 546 public int enqueueSpeechItem(int queueMode, final SpeechItem speechItem) { 547 UtteranceProgressDispatcher utterenceProgress = null; 548 if (speechItem instanceof UtteranceProgressDispatcher) { 549 utterenceProgress = (UtteranceProgressDispatcher) speechItem; 550 } 551 552 if (!speechItem.isValid()) { 553 if (utterenceProgress != null) { 554 utterenceProgress.dispatchOnError( 555 TextToSpeechClient.Status.ERROR_INVALID_REQUEST); 556 } 557 return TextToSpeech.ERROR; 558 } 559 560 if (queueMode == TextToSpeech.QUEUE_FLUSH) { 561 stopForApp(speechItem.getCallerIdentity()); 562 } else if (queueMode == TextToSpeech.QUEUE_DESTROY) { 563 stopAll(); 564 } 565 Runnable runnable = new Runnable() { 566 @Override 567 public void run() { 568 setCurrentSpeechItem(speechItem); 569 speechItem.play(); 570 setCurrentSpeechItem(null); 571 } 572 }; 573 Message msg = Message.obtain(this, runnable); 574 575 // The obj is used to remove all callbacks from the given app in 576 // stopForApp(String). 577 // 578 // Note that this string is interned, so the == comparison works. 579 msg.obj = speechItem.getCallerIdentity(); 580 581 if (sendMessage(msg)) { 582 return TextToSpeech.SUCCESS; 583 } else { 584 Log.w(TAG, "SynthThread has quit"); 585 if (utterenceProgress != null) { 586 utterenceProgress.dispatchOnError(TextToSpeechClient.Status.ERROR_SERVICE); 587 } 588 return TextToSpeech.ERROR; 589 } 590 } 591 592 /** 593 * Stops all speech output and removes any utterances still in the queue for 594 * the calling app. 595 * 596 * Called on a service binder thread. 597 */ 598 public int stopForApp(Object callerIdentity) { 599 if (callerIdentity == null) { 600 return TextToSpeech.ERROR; 601 } 602 603 removeCallbacksAndMessages(callerIdentity); 604 // This stops writing data to the file / or publishing 605 // items to the audio playback handler. 606 // 607 // Note that the current speech item must be removed only if it 608 // belongs to the callingApp, else the item will be "orphaned" and 609 // not stopped correctly if a stop request comes along for the item 610 // from the app it belongs to. 611 SpeechItem current = maybeRemoveCurrentSpeechItem(callerIdentity); 612 if (current != null) { 613 current.stop(); 614 } 615 616 // Remove any enqueued audio too. 617 mAudioPlaybackHandler.stopForApp(callerIdentity); 618 619 return TextToSpeech.SUCCESS; 620 } 621 622 public int stopAll() { 623 // Stop the current speech item unconditionally . 624 SpeechItem current = setCurrentSpeechItem(null); 625 if (current != null) { 626 current.stop(); 627 } 628 // Remove all other items from the queue. 629 removeCallbacksAndMessages(null); 630 // Remove all pending playback as well. 631 mAudioPlaybackHandler.stop(); 632 633 return TextToSpeech.SUCCESS; 634 } 635 } 636 637 interface UtteranceProgressDispatcher { 638 public void dispatchOnFallback(); 639 public void dispatchOnStop(); 640 public void dispatchOnSuccess(); 641 public void dispatchOnStart(); 642 public void dispatchOnError(int errorCode); 643 } 644 645 /** 646 * An item in the synth thread queue. 647 */ 648 private abstract class SpeechItem { 649 private final Object mCallerIdentity; 650 private final int mCallerUid; 651 private final int mCallerPid; 652 private boolean mStarted = false; 653 private boolean mStopped = false; 654 655 public SpeechItem(Object caller, int callerUid, int callerPid) { 656 mCallerIdentity = caller; 657 mCallerUid = callerUid; 658 mCallerPid = callerPid; 659 } 660 661 public Object getCallerIdentity() { 662 return mCallerIdentity; 663 } 664 665 666 public int getCallerUid() { 667 return mCallerUid; 668 } 669 670 public int getCallerPid() { 671 return mCallerPid; 672 } 673 674 /** 675 * Checker whether the item is valid. If this method returns false, the item should not 676 * be played. 677 */ 678 public abstract boolean isValid(); 679 680 /** 681 * Plays the speech item. Blocks until playback is finished. 682 * Must not be called more than once. 683 * 684 * Only called on the synthesis thread. 685 */ 686 public void play() { 687 synchronized (this) { 688 if (mStarted) { 689 throw new IllegalStateException("play() called twice"); 690 } 691 mStarted = true; 692 } 693 playImpl(); 694 } 695 696 protected abstract void playImpl(); 697 698 /** 699 * Stops the speech item. 700 * Must not be called more than once. 701 * 702 * Can be called on multiple threads, but not on the synthesis thread. 703 */ 704 public void stop() { 705 synchronized (this) { 706 if (mStopped) { 707 throw new IllegalStateException("stop() called twice"); 708 } 709 mStopped = true; 710 } 711 stopImpl(); 712 } 713 714 protected abstract void stopImpl(); 715 716 protected synchronized boolean isStopped() { 717 return mStopped; 718 } 719 } 720 721 /** 722 * An item in the synth thread queue that process utterance (and call back to client about 723 * progress). 724 */ 725 private abstract class UtteranceSpeechItem extends SpeechItem 726 implements UtteranceProgressDispatcher { 727 728 public UtteranceSpeechItem(Object caller, int callerUid, int callerPid) { 729 super(caller, callerUid, callerPid); 730 } 731 732 @Override 733 public void dispatchOnSuccess() { 734 final String utteranceId = getUtteranceId(); 735 if (utteranceId != null) { 736 mCallbacks.dispatchOnSuccess(getCallerIdentity(), utteranceId); 737 } 738 } 739 740 @Override 741 public void dispatchOnStop() { 742 final String utteranceId = getUtteranceId(); 743 if (utteranceId != null) { 744 mCallbacks.dispatchOnStop(getCallerIdentity(), utteranceId); 745 } 746 } 747 748 @Override 749 public void dispatchOnFallback() { 750 final String utteranceId = getUtteranceId(); 751 if (utteranceId != null) { 752 mCallbacks.dispatchOnFallback(getCallerIdentity(), utteranceId); 753 } 754 } 755 756 @Override 757 public void dispatchOnStart() { 758 final String utteranceId = getUtteranceId(); 759 if (utteranceId != null) { 760 mCallbacks.dispatchOnStart(getCallerIdentity(), utteranceId); 761 } 762 } 763 764 @Override 765 public void dispatchOnError(int errorCode) { 766 final String utteranceId = getUtteranceId(); 767 if (utteranceId != null) { 768 mCallbacks.dispatchOnError(getCallerIdentity(), utteranceId, errorCode); 769 } 770 } 771 772 abstract public String getUtteranceId(); 773 774 String getStringParam(Bundle params, String key, String defaultValue) { 775 return params == null ? defaultValue : params.getString(key, defaultValue); 776 } 777 778 int getIntParam(Bundle params, String key, int defaultValue) { 779 return params == null ? defaultValue : params.getInt(key, defaultValue); 780 } 781 782 float getFloatParam(Bundle params, String key, float defaultValue) { 783 return params == null ? defaultValue : params.getFloat(key, defaultValue); 784 } 785 } 786 787 /** 788 * UtteranceSpeechItem for V1 API speech items. V1 API speech items keep 789 * synthesis parameters in a single Bundle passed as parameter. This class 790 * allow subclasses to access them conveniently. 791 */ 792 private abstract class SpeechItemV1 extends UtteranceSpeechItem { 793 protected final Bundle mParams; 794 795 SpeechItemV1(Object callerIdentity, int callerUid, int callerPid, 796 Bundle params) { 797 super(callerIdentity, callerUid, callerPid); 798 mParams = params; 799 } 800 801 boolean hasLanguage() { 802 return !TextUtils.isEmpty(getStringParam(mParams, Engine.KEY_PARAM_LANGUAGE, null)); 803 } 804 805 int getSpeechRate() { 806 return getIntParam(mParams, Engine.KEY_PARAM_RATE, getDefaultSpeechRate()); 807 } 808 809 int getPitch() { 810 return getIntParam(mParams, Engine.KEY_PARAM_PITCH, Engine.DEFAULT_PITCH); 811 } 812 813 @Override 814 public String getUtteranceId() { 815 return getStringParam(mParams, Engine.KEY_PARAM_UTTERANCE_ID, null); 816 } 817 818 int getStreamType() { 819 return getIntParam(mParams, Engine.KEY_PARAM_STREAM, Engine.DEFAULT_STREAM); 820 } 821 822 float getVolume() { 823 return getFloatParam(mParams, Engine.KEY_PARAM_VOLUME, Engine.DEFAULT_VOLUME); 824 } 825 826 float getPan() { 827 return getFloatParam(mParams, Engine.KEY_PARAM_PAN, Engine.DEFAULT_PAN); 828 } 829 } 830 831 class SynthesisSpeechItemV2 extends UtteranceSpeechItem { 832 private final SynthesisRequestV2 mSynthesisRequest; 833 private AbstractSynthesisCallback mSynthesisCallback; 834 private final EventLoggerV2 mEventLogger; 835 836 public SynthesisSpeechItemV2(Object callerIdentity, int callerUid, int callerPid, 837 SynthesisRequestV2 synthesisRequest) { 838 super(callerIdentity, callerUid, callerPid); 839 840 mSynthesisRequest = synthesisRequest; 841 mEventLogger = new EventLoggerV2(synthesisRequest, callerUid, callerPid, 842 mPackageName); 843 844 updateSpeechSpeedParam(synthesisRequest); 845 } 846 847 private void updateSpeechSpeedParam(SynthesisRequestV2 synthesisRequest) { 848 Bundle voiceParams = mSynthesisRequest.getVoiceParams(); 849 850 // Inject default speech speed if needed 851 if (voiceParams.containsKey(TextToSpeechClient.Params.SPEECH_SPEED)) { 852 if (voiceParams.getFloat(TextToSpeechClient.Params.SPEECH_SPEED) <= 0) { 853 voiceParams.putFloat(TextToSpeechClient.Params.SPEECH_SPEED, 854 getDefaultSpeechRate() / 100.0f); 855 } 856 } 857 } 858 859 @Override 860 public boolean isValid() { 861 if (mSynthesisRequest.getText() == null) { 862 Log.e(TAG, "null synthesis text"); 863 return false; 864 } 865 if (mSynthesisRequest.getText().length() >= TextToSpeech.getMaxSpeechInputLength()) { 866 Log.w(TAG, "Text too long: " + mSynthesisRequest.getText().length() + " chars"); 867 return false; 868 } 869 870 return true; 871 } 872 873 @Override 874 protected void playImpl() { 875 AbstractSynthesisCallback synthesisCallback; 876 if (mEventLogger != null) { 877 mEventLogger.onRequestProcessingStart(); 878 } 879 synchronized (this) { 880 // stop() might have been called before we enter this 881 // synchronized block. 882 if (isStopped()) { 883 return; 884 } 885 mSynthesisCallback = createSynthesisCallback(); 886 synthesisCallback = mSynthesisCallback; 887 } 888 889 // Get voice info 890 VoiceInfo voiceInfo = getVoicesInfoWithName(mSynthesisRequest.getVoiceName()); 891 if (voiceInfo != null) { 892 // Primary voice 893 TextToSpeechService.this.onSynthesizeTextV2(mSynthesisRequest, voiceInfo, 894 synthesisCallback); 895 } else { 896 Log.e(TAG, "Unknown voice name:" + mSynthesisRequest.getVoiceName()); 897 synthesisCallback.error(TextToSpeechClient.Status.ERROR_INVALID_REQUEST); 898 } 899 900 // Fix for case where client called .start() & .error(), but did not called .done() 901 if (!synthesisCallback.hasFinished()) { 902 synthesisCallback.done(); 903 } 904 } 905 906 @Override 907 protected void stopImpl() { 908 AbstractSynthesisCallback synthesisCallback; 909 synchronized (this) { 910 synthesisCallback = mSynthesisCallback; 911 } 912 if (synthesisCallback != null) { 913 // If the synthesis callback is null, it implies that we haven't 914 // entered the synchronized(this) block in playImpl which in 915 // turn implies that synthesis would not have started. 916 synthesisCallback.stop(); 917 TextToSpeechService.this.onStop(); 918 } 919 } 920 921 protected AbstractSynthesisCallback createSynthesisCallback() { 922 return new PlaybackSynthesisCallback(getStreamType(), getVolume(), getPan(), 923 mAudioPlaybackHandler, this, getCallerIdentity(), mEventLogger, 924 implementsV2API()); 925 } 926 927 private int getStreamType() { 928 return getIntParam(mSynthesisRequest.getAudioParams(), 929 TextToSpeechClient.Params.AUDIO_PARAM_STREAM, 930 Engine.DEFAULT_STREAM); 931 } 932 933 private float getVolume() { 934 return getFloatParam(mSynthesisRequest.getAudioParams(), 935 TextToSpeechClient.Params.AUDIO_PARAM_VOLUME, 936 Engine.DEFAULT_VOLUME); 937 } 938 939 private float getPan() { 940 return getFloatParam(mSynthesisRequest.getAudioParams(), 941 TextToSpeechClient.Params.AUDIO_PARAM_PAN, 942 Engine.DEFAULT_PAN); 943 } 944 945 @Override 946 public String getUtteranceId() { 947 return mSynthesisRequest.getUtteranceId(); 948 } 949 } 950 951 private class SynthesisToFileOutputStreamSpeechItemV2 extends SynthesisSpeechItemV2 { 952 private final FileOutputStream mFileOutputStream; 953 954 public SynthesisToFileOutputStreamSpeechItemV2(Object callerIdentity, int callerUid, 955 int callerPid, 956 SynthesisRequestV2 synthesisRequest, 957 FileOutputStream fileOutputStream) { 958 super(callerIdentity, callerUid, callerPid, synthesisRequest); 959 mFileOutputStream = fileOutputStream; 960 } 961 962 @Override 963 protected AbstractSynthesisCallback createSynthesisCallback() { 964 return new FileSynthesisCallback(mFileOutputStream.getChannel(), 965 this, getCallerIdentity(), implementsV2API()); 966 } 967 968 @Override 969 protected void playImpl() { 970 super.playImpl(); 971 try { 972 mFileOutputStream.close(); 973 } catch(IOException e) { 974 Log.w(TAG, "Failed to close output file", e); 975 } 976 } 977 } 978 979 private class AudioSpeechItemV2 extends UtteranceSpeechItem { 980 private final AudioPlaybackQueueItem mItem; 981 private final Bundle mAudioParams; 982 private final String mUtteranceId; 983 984 public AudioSpeechItemV2(Object callerIdentity, int callerUid, int callerPid, 985 String utteranceId, Bundle audioParams, Uri uri) { 986 super(callerIdentity, callerUid, callerPid); 987 mUtteranceId = utteranceId; 988 mAudioParams = audioParams; 989 mItem = new AudioPlaybackQueueItem(this, getCallerIdentity(), 990 TextToSpeechService.this, uri, getStreamType()); 991 } 992 993 @Override 994 public boolean isValid() { 995 return true; 996 } 997 998 @Override 999 protected void playImpl() { 1000 mAudioPlaybackHandler.enqueue(mItem); 1001 } 1002 1003 @Override 1004 protected void stopImpl() { 1005 // Do nothing. 1006 } 1007 1008 protected int getStreamType() { 1009 return mAudioParams.getInt(TextToSpeechClient.Params.AUDIO_PARAM_STREAM); 1010 } 1011 1012 public String getUtteranceId() { 1013 return mUtteranceId; 1014 } 1015 } 1016 1017 1018 class SynthesisSpeechItemV1 extends SpeechItemV1 { 1019 // Never null. 1020 private final String mText; 1021 private final SynthesisRequest mSynthesisRequest; 1022 private final String[] mDefaultLocale; 1023 // Non null after synthesis has started, and all accesses 1024 // guarded by 'this'. 1025 private AbstractSynthesisCallback mSynthesisCallback; 1026 private final EventLoggerV1 mEventLogger; 1027 private final int mCallerUid; 1028 1029 public SynthesisSpeechItemV1(Object callerIdentity, int callerUid, int callerPid, 1030 Bundle params, String text) { 1031 super(callerIdentity, callerUid, callerPid, params); 1032 mText = text; 1033 mCallerUid = callerUid; 1034 mSynthesisRequest = new SynthesisRequest(mText, mParams); 1035 mDefaultLocale = getSettingsLocale(); 1036 setRequestParams(mSynthesisRequest); 1037 mEventLogger = new EventLoggerV1(mSynthesisRequest, callerUid, callerPid, 1038 mPackageName); 1039 } 1040 1041 public String getText() { 1042 return mText; 1043 } 1044 1045 @Override 1046 public boolean isValid() { 1047 if (mText == null) { 1048 Log.e(TAG, "null synthesis text"); 1049 return false; 1050 } 1051 if (mText.length() >= TextToSpeech.getMaxSpeechInputLength()) { 1052 Log.w(TAG, "Text too long: " + mText.length() + " chars"); 1053 return false; 1054 } 1055 return true; 1056 } 1057 1058 @Override 1059 protected void playImpl() { 1060 AbstractSynthesisCallback synthesisCallback; 1061 mEventLogger.onRequestProcessingStart(); 1062 synchronized (this) { 1063 // stop() might have been called before we enter this 1064 // synchronized block. 1065 if (isStopped()) { 1066 return; 1067 } 1068 mSynthesisCallback = createSynthesisCallback(); 1069 synthesisCallback = mSynthesisCallback; 1070 } 1071 1072 TextToSpeechService.this.onSynthesizeText(mSynthesisRequest, synthesisCallback); 1073 1074 // Fix for case where client called .start() & .error(), but did not called .done() 1075 if (synthesisCallback.hasStarted() && !synthesisCallback.hasFinished()) { 1076 synthesisCallback.done(); 1077 } 1078 } 1079 1080 protected AbstractSynthesisCallback createSynthesisCallback() { 1081 return new PlaybackSynthesisCallback(getStreamType(), getVolume(), getPan(), 1082 mAudioPlaybackHandler, this, getCallerIdentity(), mEventLogger, false); 1083 } 1084 1085 private void setRequestParams(SynthesisRequest request) { 1086 request.setLanguage(getLanguage(), getCountry(), getVariant()); 1087 request.setSpeechRate(getSpeechRate()); 1088 request.setCallerUid(mCallerUid); 1089 request.setPitch(getPitch()); 1090 } 1091 1092 @Override 1093 protected void stopImpl() { 1094 AbstractSynthesisCallback synthesisCallback; 1095 synchronized (this) { 1096 synthesisCallback = mSynthesisCallback; 1097 } 1098 if (synthesisCallback != null) { 1099 // If the synthesis callback is null, it implies that we haven't 1100 // entered the synchronized(this) block in playImpl which in 1101 // turn implies that synthesis would not have started. 1102 synthesisCallback.stop(); 1103 TextToSpeechService.this.onStop(); 1104 } 1105 } 1106 1107 private String getCountry() { 1108 if (!hasLanguage()) return mDefaultLocale[1]; 1109 return getStringParam(mParams, Engine.KEY_PARAM_COUNTRY, ""); 1110 } 1111 1112 private String getVariant() { 1113 if (!hasLanguage()) return mDefaultLocale[2]; 1114 return getStringParam(mParams, Engine.KEY_PARAM_VARIANT, ""); 1115 } 1116 1117 public String getLanguage() { 1118 return getStringParam(mParams, Engine.KEY_PARAM_LANGUAGE, mDefaultLocale[0]); 1119 } 1120 } 1121 1122 private class SynthesisToFileOutputStreamSpeechItemV1 extends SynthesisSpeechItemV1 { 1123 private final FileOutputStream mFileOutputStream; 1124 1125 public SynthesisToFileOutputStreamSpeechItemV1(Object callerIdentity, int callerUid, 1126 int callerPid, Bundle params, String text, FileOutputStream fileOutputStream) { 1127 super(callerIdentity, callerUid, callerPid, params, text); 1128 mFileOutputStream = fileOutputStream; 1129 } 1130 1131 @Override 1132 protected AbstractSynthesisCallback createSynthesisCallback() { 1133 return new FileSynthesisCallback(mFileOutputStream.getChannel(), 1134 this, getCallerIdentity(), false); 1135 } 1136 1137 @Override 1138 protected void playImpl() { 1139 dispatchOnStart(); 1140 super.playImpl(); 1141 try { 1142 mFileOutputStream.close(); 1143 } catch(IOException e) { 1144 Log.w(TAG, "Failed to close output file", e); 1145 } 1146 } 1147 } 1148 1149 private class AudioSpeechItemV1 extends SpeechItemV1 { 1150 private final AudioPlaybackQueueItem mItem; 1151 1152 public AudioSpeechItemV1(Object callerIdentity, int callerUid, int callerPid, 1153 Bundle params, Uri uri) { 1154 super(callerIdentity, callerUid, callerPid, params); 1155 mItem = new AudioPlaybackQueueItem(this, getCallerIdentity(), 1156 TextToSpeechService.this, uri, getStreamType()); 1157 } 1158 1159 @Override 1160 public boolean isValid() { 1161 return true; 1162 } 1163 1164 @Override 1165 protected void playImpl() { 1166 mAudioPlaybackHandler.enqueue(mItem); 1167 } 1168 1169 @Override 1170 protected void stopImpl() { 1171 // Do nothing. 1172 } 1173 1174 @Override 1175 public String getUtteranceId() { 1176 return getStringParam(mParams, Engine.KEY_PARAM_UTTERANCE_ID, null); 1177 } 1178 } 1179 1180 private class SilenceSpeechItem extends UtteranceSpeechItem { 1181 private final long mDuration; 1182 private final String mUtteranceId; 1183 1184 public SilenceSpeechItem(Object callerIdentity, int callerUid, int callerPid, 1185 String utteranceId, long duration) { 1186 super(callerIdentity, callerUid, callerPid); 1187 mUtteranceId = utteranceId; 1188 mDuration = duration; 1189 } 1190 1191 @Override 1192 public boolean isValid() { 1193 return true; 1194 } 1195 1196 @Override 1197 protected void playImpl() { 1198 mAudioPlaybackHandler.enqueue(new SilencePlaybackQueueItem( 1199 this, getCallerIdentity(), mDuration)); 1200 } 1201 1202 @Override 1203 protected void stopImpl() { 1204 1205 } 1206 1207 @Override 1208 public String getUtteranceId() { 1209 return mUtteranceId; 1210 } 1211 } 1212 1213 /** 1214 * Call {@link TextToSpeechService#onVoicesInfoChange} on synthesis thread. 1215 */ 1216 private class VoicesInfoChangeItem extends SpeechItem { 1217 public VoicesInfoChangeItem() { 1218 super(null, 0, 0); // It's never initiated by an user 1219 } 1220 1221 @Override 1222 public boolean isValid() { 1223 return true; 1224 } 1225 1226 @Override 1227 protected void playImpl() { 1228 TextToSpeechService.this.onVoicesInfoChange(); 1229 } 1230 1231 @Override 1232 protected void stopImpl() { 1233 // No-op 1234 } 1235 } 1236 1237 /** 1238 * Call {@link TextToSpeechService#onLoadLanguage} on synth thread. 1239 */ 1240 private class LoadLanguageItem extends SpeechItem { 1241 private final String mLanguage; 1242 private final String mCountry; 1243 private final String mVariant; 1244 1245 public LoadLanguageItem(Object callerIdentity, int callerUid, int callerPid, 1246 String language, String country, String variant) { 1247 super(callerIdentity, callerUid, callerPid); 1248 mLanguage = language; 1249 mCountry = country; 1250 mVariant = variant; 1251 } 1252 1253 @Override 1254 public boolean isValid() { 1255 return true; 1256 } 1257 1258 @Override 1259 protected void playImpl() { 1260 TextToSpeechService.this.onLoadLanguage(mLanguage, mCountry, mVariant); 1261 } 1262 1263 @Override 1264 protected void stopImpl() { 1265 // No-op 1266 } 1267 } 1268 1269 @Override 1270 public IBinder onBind(Intent intent) { 1271 if (TextToSpeech.Engine.INTENT_ACTION_TTS_SERVICE.equals(intent.getAction())) { 1272 return mBinder; 1273 } 1274 return null; 1275 } 1276 1277 /** 1278 * Binder returned from {@code #onBind(Intent)}. The methods in this class can be 1279 * called called from several different threads. 1280 */ 1281 // NOTE: All calls that are passed in a calling app are interned so that 1282 // they can be used as message objects (which are tested for equality using ==). 1283 private final ITextToSpeechService.Stub mBinder = new ITextToSpeechService.Stub() { 1284 @Override 1285 public int speak(IBinder caller, String text, int queueMode, Bundle params) { 1286 if (!checkNonNull(caller, text, params)) { 1287 return TextToSpeech.ERROR; 1288 } 1289 1290 SpeechItem item = new SynthesisSpeechItemV1(caller, 1291 Binder.getCallingUid(), Binder.getCallingPid(), params, text); 1292 return mSynthHandler.enqueueSpeechItem(queueMode, item); 1293 } 1294 1295 @Override 1296 public int synthesizeToFileDescriptor(IBinder caller, String text, ParcelFileDescriptor 1297 fileDescriptor, Bundle params) { 1298 if (!checkNonNull(caller, text, fileDescriptor, params)) { 1299 return TextToSpeech.ERROR; 1300 } 1301 1302 // In test env, ParcelFileDescriptor instance may be EXACTLY the same 1303 // one that is used by client. And it will be closed by a client, thus 1304 // preventing us from writing anything to it. 1305 final ParcelFileDescriptor sameFileDescriptor = ParcelFileDescriptor.adoptFd( 1306 fileDescriptor.detachFd()); 1307 1308 SpeechItem item = new SynthesisToFileOutputStreamSpeechItemV1(caller, 1309 Binder.getCallingUid(), Binder.getCallingPid(), params, text, 1310 new ParcelFileDescriptor.AutoCloseOutputStream(sameFileDescriptor)); 1311 return mSynthHandler.enqueueSpeechItem(TextToSpeech.QUEUE_ADD, item); 1312 } 1313 1314 @Override 1315 public int playAudio(IBinder caller, Uri audioUri, int queueMode, Bundle params) { 1316 if (!checkNonNull(caller, audioUri, params)) { 1317 return TextToSpeech.ERROR; 1318 } 1319 1320 SpeechItem item = new AudioSpeechItemV1(caller, 1321 Binder.getCallingUid(), Binder.getCallingPid(), params, audioUri); 1322 return mSynthHandler.enqueueSpeechItem(queueMode, item); 1323 } 1324 1325 @Override 1326 public int playSilence(IBinder caller, long duration, int queueMode, String utteranceId) { 1327 if (!checkNonNull(caller)) { 1328 return TextToSpeech.ERROR; 1329 } 1330 1331 SpeechItem item = new SilenceSpeechItem(caller, 1332 Binder.getCallingUid(), Binder.getCallingPid(), utteranceId, duration); 1333 return mSynthHandler.enqueueSpeechItem(queueMode, item); 1334 } 1335 1336 @Override 1337 public boolean isSpeaking() { 1338 return mSynthHandler.isSpeaking() || mAudioPlaybackHandler.isSpeaking(); 1339 } 1340 1341 @Override 1342 public int stop(IBinder caller) { 1343 if (!checkNonNull(caller)) { 1344 return TextToSpeech.ERROR; 1345 } 1346 1347 return mSynthHandler.stopForApp(caller); 1348 } 1349 1350 @Override 1351 public String[] getLanguage() { 1352 return onGetLanguage(); 1353 } 1354 1355 @Override 1356 public String[] getClientDefaultLanguage() { 1357 return getSettingsLocale(); 1358 } 1359 1360 /* 1361 * If defaults are enforced, then no language is "available" except 1362 * perhaps the default language selected by the user. 1363 */ 1364 @Override 1365 public int isLanguageAvailable(String lang, String country, String variant) { 1366 if (!checkNonNull(lang)) { 1367 return TextToSpeech.ERROR; 1368 } 1369 1370 return onIsLanguageAvailable(lang, country, variant); 1371 } 1372 1373 @Override 1374 public String[] getFeaturesForLanguage(String lang, String country, String variant) { 1375 Set<String> features = onGetFeaturesForLanguage(lang, country, variant); 1376 String[] featuresArray = null; 1377 if (features != null) { 1378 featuresArray = new String[features.size()]; 1379 features.toArray(featuresArray); 1380 } else { 1381 featuresArray = new String[0]; 1382 } 1383 return featuresArray; 1384 } 1385 1386 /* 1387 * There is no point loading a non default language if defaults 1388 * are enforced. 1389 */ 1390 @Override 1391 public int loadLanguage(IBinder caller, String lang, String country, String variant) { 1392 if (!checkNonNull(lang)) { 1393 return TextToSpeech.ERROR; 1394 } 1395 int retVal = onIsLanguageAvailable(lang, country, variant); 1396 1397 if (retVal == TextToSpeech.LANG_AVAILABLE || 1398 retVal == TextToSpeech.LANG_COUNTRY_AVAILABLE || 1399 retVal == TextToSpeech.LANG_COUNTRY_VAR_AVAILABLE) { 1400 1401 SpeechItem item = new LoadLanguageItem(caller, Binder.getCallingUid(), 1402 Binder.getCallingPid(), lang, country, variant); 1403 1404 if (mSynthHandler.enqueueSpeechItem(TextToSpeech.QUEUE_ADD, item) != 1405 TextToSpeech.SUCCESS) { 1406 return TextToSpeech.ERROR; 1407 } 1408 } 1409 return retVal; 1410 } 1411 1412 @Override 1413 public void setCallback(IBinder caller, ITextToSpeechCallback cb) { 1414 // Note that passing in a null callback is a valid use case. 1415 if (!checkNonNull(caller)) { 1416 return; 1417 } 1418 1419 mCallbacks.setCallback(caller, cb); 1420 } 1421 1422 private String intern(String in) { 1423 // The input parameter will be non null. 1424 return in.intern(); 1425 } 1426 1427 private boolean checkNonNull(Object... args) { 1428 for (Object o : args) { 1429 if (o == null) return false; 1430 } 1431 return true; 1432 } 1433 1434 @Override 1435 public List<VoiceInfo> getVoicesInfo() { 1436 return TextToSpeechService.this.getVoicesInfo(); 1437 } 1438 1439 @Override 1440 public int speakV2(IBinder callingInstance, 1441 SynthesisRequestV2 request) { 1442 if (!checkNonNull(callingInstance, request)) { 1443 return TextToSpeech.ERROR; 1444 } 1445 1446 SpeechItem item = new SynthesisSpeechItemV2(callingInstance, 1447 Binder.getCallingUid(), Binder.getCallingPid(), request); 1448 return mSynthHandler.enqueueSpeechItem(TextToSpeech.QUEUE_ADD, item); 1449 } 1450 1451 @Override 1452 public int synthesizeToFileDescriptorV2(IBinder callingInstance, 1453 ParcelFileDescriptor fileDescriptor, 1454 SynthesisRequestV2 request) { 1455 if (!checkNonNull(callingInstance, request, fileDescriptor)) { 1456 return TextToSpeech.ERROR; 1457 } 1458 1459 // In test env, ParcelFileDescriptor instance may be EXACTLY the same 1460 // one that is used by client. And it will be closed by a client, thus 1461 // preventing us from writing anything to it. 1462 final ParcelFileDescriptor sameFileDescriptor = ParcelFileDescriptor.adoptFd( 1463 fileDescriptor.detachFd()); 1464 1465 SpeechItem item = new SynthesisToFileOutputStreamSpeechItemV2(callingInstance, 1466 Binder.getCallingUid(), Binder.getCallingPid(), request, 1467 new ParcelFileDescriptor.AutoCloseOutputStream(sameFileDescriptor)); 1468 return mSynthHandler.enqueueSpeechItem(TextToSpeech.QUEUE_ADD, item); 1469 1470 } 1471 1472 @Override 1473 public int playAudioV2( 1474 IBinder callingInstance, Uri audioUri, String utteranceId, 1475 Bundle systemParameters) { 1476 if (!checkNonNull(callingInstance, audioUri, systemParameters)) { 1477 return TextToSpeech.ERROR; 1478 } 1479 1480 SpeechItem item = new AudioSpeechItemV2(callingInstance, 1481 Binder.getCallingUid(), Binder.getCallingPid(), utteranceId, systemParameters, 1482 audioUri); 1483 return mSynthHandler.enqueueSpeechItem(TextToSpeech.QUEUE_ADD, item); 1484 } 1485 }; 1486 1487 private class CallbackMap extends RemoteCallbackList<ITextToSpeechCallback> { 1488 private final HashMap<IBinder, ITextToSpeechCallback> mCallerToCallback 1489 = new HashMap<IBinder, ITextToSpeechCallback>(); 1490 1491 public void setCallback(IBinder caller, ITextToSpeechCallback cb) { 1492 synchronized (mCallerToCallback) { 1493 ITextToSpeechCallback old; 1494 if (cb != null) { 1495 register(cb, caller); 1496 old = mCallerToCallback.put(caller, cb); 1497 } else { 1498 old = mCallerToCallback.remove(caller); 1499 } 1500 if (old != null && old != cb) { 1501 unregister(old); 1502 } 1503 } 1504 } 1505 1506 public void dispatchOnFallback(Object callerIdentity, String utteranceId) { 1507 ITextToSpeechCallback cb = getCallbackFor(callerIdentity); 1508 if (cb == null) return; 1509 try { 1510 cb.onFallback(utteranceId); 1511 } catch (RemoteException e) { 1512 Log.e(TAG, "Callback onFallback failed: " + e); 1513 } 1514 } 1515 1516 public void dispatchOnStop(Object callerIdentity, String utteranceId) { 1517 ITextToSpeechCallback cb = getCallbackFor(callerIdentity); 1518 if (cb == null) return; 1519 try { 1520 cb.onStop(utteranceId); 1521 } catch (RemoteException e) { 1522 Log.e(TAG, "Callback onStop failed: " + e); 1523 } 1524 } 1525 1526 public void dispatchOnSuccess(Object callerIdentity, String utteranceId) { 1527 ITextToSpeechCallback cb = getCallbackFor(callerIdentity); 1528 if (cb == null) return; 1529 try { 1530 cb.onSuccess(utteranceId); 1531 } catch (RemoteException e) { 1532 Log.e(TAG, "Callback onDone failed: " + e); 1533 } 1534 } 1535 1536 public void dispatchOnStart(Object callerIdentity, String utteranceId) { 1537 ITextToSpeechCallback cb = getCallbackFor(callerIdentity); 1538 if (cb == null) return; 1539 try { 1540 cb.onStart(utteranceId); 1541 } catch (RemoteException e) { 1542 Log.e(TAG, "Callback onStart failed: " + e); 1543 } 1544 1545 } 1546 1547 public void dispatchOnError(Object callerIdentity, String utteranceId, 1548 int errorCode) { 1549 ITextToSpeechCallback cb = getCallbackFor(callerIdentity); 1550 if (cb == null) return; 1551 try { 1552 cb.onError(utteranceId, errorCode); 1553 } catch (RemoteException e) { 1554 Log.e(TAG, "Callback onError failed: " + e); 1555 } 1556 } 1557 1558 @Override 1559 public void onCallbackDied(ITextToSpeechCallback callback, Object cookie) { 1560 IBinder caller = (IBinder) cookie; 1561 synchronized (mCallerToCallback) { 1562 mCallerToCallback.remove(caller); 1563 } 1564 //mSynthHandler.stopForApp(caller); 1565 } 1566 1567 @Override 1568 public void kill() { 1569 synchronized (mCallerToCallback) { 1570 mCallerToCallback.clear(); 1571 super.kill(); 1572 } 1573 } 1574 1575 public void dispatchVoicesInfoChange(List<VoiceInfo> voicesInfo) { 1576 synchronized (mCallerToCallback) { 1577 for (ITextToSpeechCallback callback : mCallerToCallback.values()) { 1578 try { 1579 callback.onVoicesInfoChange(voicesInfo); 1580 } catch (RemoteException e) { 1581 Log.e(TAG, "Failed to request reconnect", e); 1582 } 1583 } 1584 } 1585 } 1586 1587 private ITextToSpeechCallback getCallbackFor(Object caller) { 1588 ITextToSpeechCallback cb; 1589 IBinder asBinder = (IBinder) caller; 1590 synchronized (mCallerToCallback) { 1591 cb = mCallerToCallback.get(asBinder); 1592 } 1593 1594 return cb; 1595 } 1596 } 1597} 1598