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