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