• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 boolean setCurrentSpeechItem(SpeechItem speechItem) {
506             // Do not set as current if the item has already been flushed. The check is
507             // intentionally put inside this synchronized method. Specifically, the following
508             // racy sequence between this method and stopForApp() needs to be avoided.
509             //        (this method)          (stopForApp)
510             //     1. isFlushed
511             //     2.                        startFlushingSpeechItems
512             //     3.                        maybeRemoveCurrentSpeechItem
513             //     4. set mCurrentSpeechItem
514             // If it happens, stop() is never called on the item. The guard by synchornized(this)
515             // ensures that the step 3 cannot interrupt between 1 and 4.
516             if (speechItem != null && isFlushed(speechItem)) {
517                 return false;
518             }
519             mCurrentSpeechItem = speechItem;
520             return true;
521         }
522 
removeCurrentSpeechItem()523         private synchronized SpeechItem removeCurrentSpeechItem() {
524             SpeechItem current = mCurrentSpeechItem;
525             mCurrentSpeechItem = null;
526             return current;
527         }
528 
maybeRemoveCurrentSpeechItem(Object callerIdentity)529         private synchronized SpeechItem maybeRemoveCurrentSpeechItem(Object callerIdentity) {
530             if (mCurrentSpeechItem != null &&
531                     (mCurrentSpeechItem.getCallerIdentity() == callerIdentity)) {
532                 SpeechItem current = mCurrentSpeechItem;
533                 mCurrentSpeechItem = null;
534                 return current;
535             }
536 
537             return null;
538         }
539 
isSpeaking()540         public boolean isSpeaking() {
541             return getCurrentSpeechItem() != null;
542         }
543 
quit()544         public void quit() {
545             // Don't process any more speech items
546             getLooper().quit();
547             // Stop the current speech item
548             SpeechItem current = removeCurrentSpeechItem();
549             if (current != null) {
550                 current.stop();
551             }
552             // The AudioPlaybackHandler will be destroyed by the caller.
553         }
554 
555         /**
556          * Adds a speech item to the queue.
557          *
558          * Called on a service binder thread.
559          */
enqueueSpeechItem(int queueMode, final SpeechItem speechItem)560         public int enqueueSpeechItem(int queueMode, final SpeechItem speechItem) {
561             UtteranceProgressDispatcher utterenceProgress = null;
562             if (speechItem instanceof UtteranceProgressDispatcher) {
563                 utterenceProgress = (UtteranceProgressDispatcher) speechItem;
564             }
565 
566             if (!speechItem.isValid()) {
567                 if (utterenceProgress != null) {
568                     utterenceProgress.dispatchOnError(
569                             TextToSpeech.ERROR_INVALID_REQUEST);
570                 }
571                 return TextToSpeech.ERROR;
572             }
573 
574             if (queueMode == TextToSpeech.QUEUE_FLUSH) {
575                 stopForApp(speechItem.getCallerIdentity());
576             } else if (queueMode == TextToSpeech.QUEUE_DESTROY) {
577                 stopAll();
578             }
579             Runnable runnable = new Runnable() {
580                 @Override
581                 public void run() {
582                     if (setCurrentSpeechItem(speechItem)) {
583                         speechItem.play();
584                         removeCurrentSpeechItem();
585                     } else {
586                         // The item is alreadly flushed. Stopping.
587                         speechItem.stop();
588                     }
589                 }
590             };
591             Message msg = Message.obtain(this, runnable);
592 
593             // The obj is used to remove all callbacks from the given app in
594             // stopForApp(String).
595             //
596             // Note that this string is interned, so the == comparison works.
597             msg.obj = speechItem.getCallerIdentity();
598 
599             if (sendMessage(msg)) {
600                 return TextToSpeech.SUCCESS;
601             } else {
602                 Log.w(TAG, "SynthThread has quit");
603                 if (utterenceProgress != null) {
604                     utterenceProgress.dispatchOnError(TextToSpeech.ERROR_SERVICE);
605                 }
606                 return TextToSpeech.ERROR;
607             }
608         }
609 
610         /**
611          * Stops all speech output and removes any utterances still in the queue for
612          * the calling app.
613          *
614          * Called on a service binder thread.
615          */
stopForApp(final Object callerIdentity)616         public int stopForApp(final Object callerIdentity) {
617             if (callerIdentity == null) {
618                 return TextToSpeech.ERROR;
619             }
620 
621             // Flush pending messages from callerIdentity.
622             // See setCurrentSpeechItem on a subtlety around a race condition.
623             startFlushingSpeechItems(callerIdentity);
624 
625             // This stops writing data to the file / or publishing
626             // items to the audio playback handler.
627             //
628             // Note that the current speech item must be removed only if it
629             // belongs to the callingApp, else the item will be "orphaned" and
630             // not stopped correctly if a stop request comes along for the item
631             // from the app it belongs to.
632             SpeechItem current = maybeRemoveCurrentSpeechItem(callerIdentity);
633             if (current != null) {
634                 current.stop();
635             }
636 
637             // Remove any enqueued audio too.
638             mAudioPlaybackHandler.stopForApp(callerIdentity);
639 
640             // Stop flushing pending messages
641             Runnable runnable = new Runnable() {
642                 @Override
643                 public void run() {
644                     endFlushingSpeechItems(callerIdentity);
645                 }
646             };
647             sendMessage(Message.obtain(this, runnable));
648             return TextToSpeech.SUCCESS;
649         }
650 
stopAll()651         public int stopAll() {
652             // Order to flush pending messages
653             startFlushingSpeechItems(null);
654 
655             // Stop the current speech item unconditionally .
656             SpeechItem current = removeCurrentSpeechItem();
657             if (current != null) {
658                 current.stop();
659             }
660             // Remove all pending playback as well.
661             mAudioPlaybackHandler.stop();
662 
663             // Message to stop flushing pending messages
664             Runnable runnable = new Runnable() {
665                 @Override
666                 public void run() {
667                     endFlushingSpeechItems(null);
668                 }
669             };
670             sendMessage(Message.obtain(this, runnable));
671 
672 
673             return TextToSpeech.SUCCESS;
674         }
675     }
676 
677     interface UtteranceProgressDispatcher {
dispatchOnStop()678         void dispatchOnStop();
679 
dispatchOnSuccess()680         void dispatchOnSuccess();
681 
dispatchOnStart()682         void dispatchOnStart();
683 
dispatchOnError(int errorCode)684         void dispatchOnError(int errorCode);
685 
dispatchOnBeginSynthesis(int sampleRateInHz, int audioFormat, int channelCount)686         void dispatchOnBeginSynthesis(int sampleRateInHz, int audioFormat, int channelCount);
687 
dispatchOnAudioAvailable(byte[] audio)688         void dispatchOnAudioAvailable(byte[] audio);
689 
dispatchOnRangeStart(int start, int end, int frame)690         public void dispatchOnRangeStart(int start, int end, int frame);
691     }
692 
693     /** Set of parameters affecting audio output. */
694     static class AudioOutputParams {
695         /**
696          * Audio session identifier. May be used to associate audio playback with one of the
697          * {@link android.media.audiofx.AudioEffect} objects. If not specified by client,
698          * it should be equal to {@link AudioManager#AUDIO_SESSION_ID_GENERATE}.
699          */
700         public final int mSessionId;
701 
702         /**
703          * Volume, in the range [0.0f, 1.0f]. The default value is
704          * {@link TextToSpeech.Engine#DEFAULT_VOLUME} (1.0f).
705          */
706         public final float mVolume;
707 
708         /**
709          * Left/right position of the audio, in the range [-1.0f, 1.0f].
710          * The default value is {@link TextToSpeech.Engine#DEFAULT_PAN} (0.0f).
711          */
712         public final float mPan;
713 
714 
715         /**
716          * Audio attributes, set by {@link TextToSpeech#setAudioAttributes}
717          * or created from the value of {@link TextToSpeech.Engine#KEY_PARAM_STREAM}.
718          */
719         public final AudioAttributes mAudioAttributes;
720 
721         /** Create AudioOutputParams with default values */
AudioOutputParams()722         AudioOutputParams() {
723             mSessionId = AudioManager.AUDIO_SESSION_ID_GENERATE;
724             mVolume = Engine.DEFAULT_VOLUME;
725             mPan = Engine.DEFAULT_PAN;
726             mAudioAttributes = null;
727         }
728 
AudioOutputParams(int sessionId, float volume, float pan, AudioAttributes audioAttributes)729         AudioOutputParams(int sessionId, float volume, float pan,
730                 AudioAttributes audioAttributes) {
731             mSessionId = sessionId;
732             mVolume = volume;
733             mPan = pan;
734             mAudioAttributes = audioAttributes;
735         }
736 
737         /** Create AudioOutputParams from A {@link SynthesisRequest#getParams()} bundle */
createFromParamsBundle(Bundle paramsBundle, boolean isSpeech)738         static AudioOutputParams createFromParamsBundle(Bundle paramsBundle, boolean isSpeech) {
739             if (paramsBundle == null) {
740                 return new AudioOutputParams();
741             }
742 
743             AudioAttributes audioAttributes =
744                     (AudioAttributes) paramsBundle.getParcelable(
745                             Engine.KEY_PARAM_AUDIO_ATTRIBUTES);
746             if (audioAttributes == null) {
747                 int streamType = paramsBundle.getInt(
748                         Engine.KEY_PARAM_STREAM, Engine.DEFAULT_STREAM);
749                 audioAttributes = (new AudioAttributes.Builder())
750                         .setLegacyStreamType(streamType)
751                         .setContentType((isSpeech ?
752                                 AudioAttributes.CONTENT_TYPE_SPEECH :
753                                 AudioAttributes.CONTENT_TYPE_SONIFICATION))
754                         .build();
755             }
756 
757             return new AudioOutputParams(
758                     paramsBundle.getInt(
759                             Engine.KEY_PARAM_SESSION_ID,
760                             AudioManager.AUDIO_SESSION_ID_GENERATE),
761                     paramsBundle.getFloat(
762                             Engine.KEY_PARAM_VOLUME,
763                             Engine.DEFAULT_VOLUME),
764                     paramsBundle.getFloat(
765                             Engine.KEY_PARAM_PAN,
766                             Engine.DEFAULT_PAN),
767                     audioAttributes);
768         }
769     }
770 
771 
772     /**
773      * An item in the synth thread queue.
774      */
775     private abstract class SpeechItem {
776         private final Object mCallerIdentity;
777         private final int mCallerUid;
778         private final int mCallerPid;
779         private boolean mStarted = false;
780         private boolean mStopped = false;
781 
SpeechItem(Object caller, int callerUid, int callerPid)782         public SpeechItem(Object caller, int callerUid, int callerPid) {
783             mCallerIdentity = caller;
784             mCallerUid = callerUid;
785             mCallerPid = callerPid;
786         }
787 
getCallerIdentity()788         public Object getCallerIdentity() {
789             return mCallerIdentity;
790         }
791 
getCallerUid()792         public int getCallerUid() {
793             return mCallerUid;
794         }
795 
getCallerPid()796         public int getCallerPid() {
797             return mCallerPid;
798         }
799 
800         /**
801          * Checker whether the item is valid. If this method returns false, the item should not
802          * be played.
803          */
isValid()804         public abstract boolean isValid();
805 
806         /**
807          * Plays the speech item. Blocks until playback is finished.
808          * Must not be called more than once.
809          *
810          * Only called on the synthesis thread.
811          */
play()812         public void play() {
813             synchronized (this) {
814                 if (mStarted) {
815                     throw new IllegalStateException("play() called twice");
816                 }
817                 mStarted = true;
818             }
819             playImpl();
820         }
821 
playImpl()822         protected abstract void playImpl();
823 
824         /**
825          * Stops the speech item.
826          * Must not be called more than once.
827          *
828          * Can be called on multiple threads,  but not on the synthesis thread.
829          */
stop()830         public void stop() {
831             synchronized (this) {
832                 if (mStopped) {
833                     throw new IllegalStateException("stop() called twice");
834                 }
835                 mStopped = true;
836             }
837             stopImpl();
838         }
839 
stopImpl()840         protected abstract void stopImpl();
841 
isStopped()842         protected synchronized boolean isStopped() {
843              return mStopped;
844         }
845 
isStarted()846         protected synchronized boolean isStarted() {
847             return mStarted;
848        }
849     }
850 
851     /**
852      * An item in the synth thread queue that process utterance (and call back to client about
853      * progress).
854      */
855     private abstract class UtteranceSpeechItem extends SpeechItem
856         implements UtteranceProgressDispatcher  {
857 
UtteranceSpeechItem(Object caller, int callerUid, int callerPid)858         public UtteranceSpeechItem(Object caller, int callerUid, int callerPid) {
859             super(caller, callerUid, callerPid);
860         }
861 
862         @Override
dispatchOnSuccess()863         public void dispatchOnSuccess() {
864             final String utteranceId = getUtteranceId();
865             if (utteranceId != null) {
866                 mCallbacks.dispatchOnSuccess(getCallerIdentity(), utteranceId);
867             }
868         }
869 
870         @Override
dispatchOnStop()871         public void dispatchOnStop() {
872             final String utteranceId = getUtteranceId();
873             if (utteranceId != null) {
874                 mCallbacks.dispatchOnStop(getCallerIdentity(), utteranceId, isStarted());
875             }
876         }
877 
878         @Override
dispatchOnStart()879         public void dispatchOnStart() {
880             final String utteranceId = getUtteranceId();
881             if (utteranceId != null) {
882                 mCallbacks.dispatchOnStart(getCallerIdentity(), utteranceId);
883             }
884         }
885 
886         @Override
dispatchOnError(int errorCode)887         public void dispatchOnError(int errorCode) {
888             final String utteranceId = getUtteranceId();
889             if (utteranceId != null) {
890                 mCallbacks.dispatchOnError(getCallerIdentity(), utteranceId, errorCode);
891             }
892         }
893 
894         @Override
dispatchOnBeginSynthesis(int sampleRateInHz, int audioFormat, int channelCount)895         public void dispatchOnBeginSynthesis(int sampleRateInHz, int audioFormat, int channelCount) {
896             final String utteranceId = getUtteranceId();
897             if (utteranceId != null) {
898                 mCallbacks.dispatchOnBeginSynthesis(getCallerIdentity(), utteranceId, sampleRateInHz, audioFormat, channelCount);
899             }
900         }
901 
902         @Override
dispatchOnAudioAvailable(byte[] audio)903         public void dispatchOnAudioAvailable(byte[] audio) {
904             final String utteranceId = getUtteranceId();
905             if (utteranceId != null) {
906                 mCallbacks.dispatchOnAudioAvailable(getCallerIdentity(), utteranceId, audio);
907             }
908         }
909 
910         @Override
dispatchOnRangeStart(int start, int end, int frame)911         public void dispatchOnRangeStart(int start, int end, int frame) {
912             final String utteranceId = getUtteranceId();
913             if (utteranceId != null) {
914                 mCallbacks.dispatchOnRangeStart(
915                         getCallerIdentity(), utteranceId, start, end, frame);
916             }
917         }
918 
getUtteranceId()919         abstract public String getUtteranceId();
920 
getStringParam(Bundle params, String key, String defaultValue)921         String getStringParam(Bundle params, String key, String defaultValue) {
922             return params == null ? defaultValue : params.getString(key, defaultValue);
923         }
924 
getIntParam(Bundle params, String key, int defaultValue)925         int getIntParam(Bundle params, String key, int defaultValue) {
926             return params == null ? defaultValue : params.getInt(key, defaultValue);
927         }
928 
getFloatParam(Bundle params, String key, float defaultValue)929         float getFloatParam(Bundle params, String key, float defaultValue) {
930             return params == null ? defaultValue : params.getFloat(key, defaultValue);
931         }
932     }
933 
934     /**
935      * Synthesis parameters are kept in a single Bundle passed as parameter. This class allow
936      * subclasses to access them conveniently.
937      */
938     private abstract class UtteranceSpeechItemWithParams extends UtteranceSpeechItem {
939         protected final Bundle mParams;
940         protected final String mUtteranceId;
941 
UtteranceSpeechItemWithParams( Object callerIdentity, int callerUid, int callerPid, Bundle params, String utteranceId)942         UtteranceSpeechItemWithParams(
943                 Object callerIdentity,
944                 int callerUid,
945                 int callerPid,
946                 Bundle params,
947                 String utteranceId) {
948             super(callerIdentity, callerUid, callerPid);
949             mParams = params;
950             mUtteranceId = utteranceId;
951         }
952 
hasLanguage()953         boolean hasLanguage() {
954             return !TextUtils.isEmpty(getStringParam(mParams, Engine.KEY_PARAM_LANGUAGE, null));
955         }
956 
getSpeechRate()957         int getSpeechRate() {
958             return getIntParam(mParams, Engine.KEY_PARAM_RATE, getDefaultSpeechRate());
959         }
960 
getPitch()961         int getPitch() {
962             return getIntParam(mParams, Engine.KEY_PARAM_PITCH, getDefaultPitch());
963         }
964 
965         @Override
getUtteranceId()966         public String getUtteranceId() {
967             return mUtteranceId;
968         }
969 
getAudioParams()970         AudioOutputParams getAudioParams() {
971             return AudioOutputParams.createFromParamsBundle(mParams, true);
972         }
973     }
974 
975     class SynthesisSpeechItem extends UtteranceSpeechItemWithParams {
976         // Never null.
977         private final CharSequence mText;
978         private final SynthesisRequest mSynthesisRequest;
979         private final String[] mDefaultLocale;
980         // Non null after synthesis has started, and all accesses
981         // guarded by 'this'.
982         private AbstractSynthesisCallback mSynthesisCallback;
983         private final EventLogger mEventLogger;
984         private final int mCallerUid;
985 
SynthesisSpeechItem( Object callerIdentity, int callerUid, int callerPid, Bundle params, String utteranceId, CharSequence text)986         public SynthesisSpeechItem(
987                 Object callerIdentity,
988                 int callerUid,
989                 int callerPid,
990                 Bundle params,
991                 String utteranceId,
992                 CharSequence text) {
993             super(callerIdentity, callerUid, callerPid, params, utteranceId);
994             mText = text;
995             mCallerUid = callerUid;
996             mSynthesisRequest = new SynthesisRequest(mText, mParams);
997             mDefaultLocale = getSettingsLocale();
998             setRequestParams(mSynthesisRequest);
999             mEventLogger = new EventLogger(mSynthesisRequest, callerUid, callerPid, mPackageName);
1000         }
1001 
getText()1002         public CharSequence getText() {
1003             return mText;
1004         }
1005 
1006         @Override
isValid()1007         public boolean isValid() {
1008             if (mText == null) {
1009                 Log.e(TAG, "null synthesis text");
1010                 return false;
1011             }
1012             if (mText.length() > TextToSpeech.getMaxSpeechInputLength()) {
1013                 Log.w(TAG, "Text too long: " + mText.length() + " chars");
1014                 return false;
1015             }
1016             return true;
1017         }
1018 
1019         @Override
playImpl()1020         protected void playImpl() {
1021             AbstractSynthesisCallback synthesisCallback;
1022             mEventLogger.onRequestProcessingStart();
1023             synchronized (this) {
1024                 // stop() might have been called before we enter this
1025                 // synchronized block.
1026                 if (isStopped()) {
1027                     return;
1028                 }
1029                 mSynthesisCallback = createSynthesisCallback();
1030                 synthesisCallback = mSynthesisCallback;
1031             }
1032 
1033             TextToSpeechService.this.onSynthesizeText(mSynthesisRequest, synthesisCallback);
1034 
1035             // Fix for case where client called .start() & .error(), but did not called .done()
1036             if (synthesisCallback.hasStarted() && !synthesisCallback.hasFinished()) {
1037                 synthesisCallback.done();
1038             }
1039         }
1040 
createSynthesisCallback()1041         protected AbstractSynthesisCallback createSynthesisCallback() {
1042             return new PlaybackSynthesisCallback(getAudioParams(),
1043                     mAudioPlaybackHandler, this, getCallerIdentity(), mEventLogger, false);
1044         }
1045 
setRequestParams(SynthesisRequest request)1046         private void setRequestParams(SynthesisRequest request) {
1047             String voiceName = getVoiceName();
1048             request.setLanguage(getLanguage(), getCountry(), getVariant());
1049             if (!TextUtils.isEmpty(voiceName)) {
1050                 request.setVoiceName(getVoiceName());
1051             }
1052             request.setSpeechRate(getSpeechRate());
1053             request.setCallerUid(mCallerUid);
1054             request.setPitch(getPitch());
1055         }
1056 
1057         @Override
stopImpl()1058         protected void stopImpl() {
1059             AbstractSynthesisCallback synthesisCallback;
1060             synchronized (this) {
1061                 synthesisCallback = mSynthesisCallback;
1062             }
1063             if (synthesisCallback != null) {
1064                 // If the synthesis callback is null, it implies that we haven't
1065                 // entered the synchronized(this) block in playImpl which in
1066                 // turn implies that synthesis would not have started.
1067                 synthesisCallback.stop();
1068                 TextToSpeechService.this.onStop();
1069             } else {
1070                 dispatchOnStop();
1071             }
1072         }
1073 
getCountry()1074         private String getCountry() {
1075             if (!hasLanguage()) return mDefaultLocale[1];
1076             return getStringParam(mParams, Engine.KEY_PARAM_COUNTRY, "");
1077         }
1078 
getVariant()1079         private String getVariant() {
1080             if (!hasLanguage()) return mDefaultLocale[2];
1081             return getStringParam(mParams, Engine.KEY_PARAM_VARIANT, "");
1082         }
1083 
getLanguage()1084         public String getLanguage() {
1085             return getStringParam(mParams, Engine.KEY_PARAM_LANGUAGE, mDefaultLocale[0]);
1086         }
1087 
getVoiceName()1088         public String getVoiceName() {
1089             return getStringParam(mParams, Engine.KEY_PARAM_VOICE_NAME, "");
1090         }
1091     }
1092 
1093     private class SynthesisToFileOutputStreamSpeechItem extends SynthesisSpeechItem {
1094         private final FileOutputStream mFileOutputStream;
1095 
SynthesisToFileOutputStreamSpeechItem( Object callerIdentity, int callerUid, int callerPid, Bundle params, String utteranceId, CharSequence text, FileOutputStream fileOutputStream)1096         public SynthesisToFileOutputStreamSpeechItem(
1097                 Object callerIdentity,
1098                 int callerUid,
1099                 int callerPid,
1100                 Bundle params,
1101                 String utteranceId,
1102                 CharSequence text,
1103                 FileOutputStream fileOutputStream) {
1104             super(callerIdentity, callerUid, callerPid, params, utteranceId, text);
1105             mFileOutputStream = fileOutputStream;
1106         }
1107 
1108         @Override
createSynthesisCallback()1109         protected AbstractSynthesisCallback createSynthesisCallback() {
1110             return new FileSynthesisCallback(mFileOutputStream.getChannel(), this, false);
1111         }
1112 
1113         @Override
playImpl()1114         protected void playImpl() {
1115             super.playImpl();
1116             try {
1117               mFileOutputStream.close();
1118             } catch(IOException e) {
1119               Log.w(TAG, "Failed to close output file", e);
1120             }
1121         }
1122     }
1123 
1124     private class AudioSpeechItem extends UtteranceSpeechItemWithParams {
1125         private final AudioPlaybackQueueItem mItem;
1126 
AudioSpeechItem( Object callerIdentity, int callerUid, int callerPid, Bundle params, String utteranceId, Uri uri)1127         public AudioSpeechItem(
1128                 Object callerIdentity,
1129                 int callerUid,
1130                 int callerPid,
1131                 Bundle params,
1132                 String utteranceId,
1133                 Uri uri) {
1134             super(callerIdentity, callerUid, callerPid, params, utteranceId);
1135             mItem = new AudioPlaybackQueueItem(this, getCallerIdentity(),
1136                     TextToSpeechService.this, uri, getAudioParams());
1137         }
1138 
1139         @Override
isValid()1140         public boolean isValid() {
1141             return true;
1142         }
1143 
1144         @Override
playImpl()1145         protected void playImpl() {
1146             mAudioPlaybackHandler.enqueue(mItem);
1147         }
1148 
1149         @Override
stopImpl()1150         protected void stopImpl() {
1151             // Do nothing.
1152         }
1153 
1154         @Override
getUtteranceId()1155         public String getUtteranceId() {
1156             return getStringParam(mParams, Engine.KEY_PARAM_UTTERANCE_ID, null);
1157         }
1158 
1159         @Override
getAudioParams()1160         AudioOutputParams getAudioParams() {
1161             return AudioOutputParams.createFromParamsBundle(mParams, false);
1162         }
1163     }
1164 
1165     private class SilenceSpeechItem extends UtteranceSpeechItem {
1166         private final long mDuration;
1167         private final String mUtteranceId;
1168 
SilenceSpeechItem(Object callerIdentity, int callerUid, int callerPid, String utteranceId, long duration)1169         public SilenceSpeechItem(Object callerIdentity, int callerUid, int callerPid,
1170                 String utteranceId, long duration) {
1171             super(callerIdentity, callerUid, callerPid);
1172             mUtteranceId = utteranceId;
1173             mDuration = duration;
1174         }
1175 
1176         @Override
isValid()1177         public boolean isValid() {
1178             return true;
1179         }
1180 
1181         @Override
playImpl()1182         protected void playImpl() {
1183             mAudioPlaybackHandler.enqueue(new SilencePlaybackQueueItem(
1184                     this, getCallerIdentity(), mDuration));
1185         }
1186 
1187         @Override
stopImpl()1188         protected void stopImpl() {
1189 
1190         }
1191 
1192         @Override
getUtteranceId()1193         public String getUtteranceId() {
1194             return mUtteranceId;
1195         }
1196     }
1197 
1198     /**
1199      * Call {@link TextToSpeechService#onLoadLanguage} on synth thread.
1200      */
1201     private class LoadLanguageItem extends SpeechItem {
1202         private final String mLanguage;
1203         private final String mCountry;
1204         private final String mVariant;
1205 
LoadLanguageItem(Object callerIdentity, int callerUid, int callerPid, String language, String country, String variant)1206         public LoadLanguageItem(Object callerIdentity, int callerUid, int callerPid,
1207                 String language, String country, String variant) {
1208             super(callerIdentity, callerUid, callerPid);
1209             mLanguage = language;
1210             mCountry = country;
1211             mVariant = variant;
1212         }
1213 
1214         @Override
isValid()1215         public boolean isValid() {
1216             return true;
1217         }
1218 
1219         @Override
playImpl()1220         protected void playImpl() {
1221             TextToSpeechService.this.onLoadLanguage(mLanguage, mCountry, mVariant);
1222         }
1223 
1224         @Override
stopImpl()1225         protected void stopImpl() {
1226             // No-op
1227         }
1228     }
1229 
1230     /**
1231      * Call {@link TextToSpeechService#onLoadLanguage} on synth thread.
1232      */
1233     private class LoadVoiceItem extends SpeechItem {
1234         private final String mVoiceName;
1235 
LoadVoiceItem(Object callerIdentity, int callerUid, int callerPid, String voiceName)1236         public LoadVoiceItem(Object callerIdentity, int callerUid, int callerPid,
1237                 String voiceName) {
1238             super(callerIdentity, callerUid, callerPid);
1239             mVoiceName = voiceName;
1240         }
1241 
1242         @Override
isValid()1243         public boolean isValid() {
1244             return true;
1245         }
1246 
1247         @Override
playImpl()1248         protected void playImpl() {
1249             TextToSpeechService.this.onLoadVoice(mVoiceName);
1250         }
1251 
1252         @Override
stopImpl()1253         protected void stopImpl() {
1254             // No-op
1255         }
1256     }
1257 
1258 
1259     @Override
onBind(Intent intent)1260     public IBinder onBind(Intent intent) {
1261         if (TextToSpeech.Engine.INTENT_ACTION_TTS_SERVICE.equals(intent.getAction())) {
1262             return mBinder;
1263         }
1264         return null;
1265     }
1266 
1267     /**
1268      * Binder returned from {@code #onBind(Intent)}. The methods in this class can be called called
1269      * from several different threads.
1270      */
1271     // NOTE: All calls that are passed in a calling app are interned so that
1272     // they can be used as message objects (which are tested for equality using ==).
1273     private final ITextToSpeechService.Stub mBinder =
1274             new ITextToSpeechService.Stub() {
1275                 @Override
1276                 public int speak(
1277                         IBinder caller,
1278                         CharSequence text,
1279                         int queueMode,
1280                         Bundle params,
1281                         String utteranceId) {
1282                     if (!checkNonNull(caller, text, params)) {
1283                         return TextToSpeech.ERROR;
1284                     }
1285 
1286                     SpeechItem item =
1287                             new SynthesisSpeechItem(
1288                                     caller,
1289                                     Binder.getCallingUid(),
1290                                     Binder.getCallingPid(),
1291                                     params,
1292                                     utteranceId,
1293                                     text);
1294                     return mSynthHandler.enqueueSpeechItem(queueMode, item);
1295                 }
1296 
1297                 @Override
1298                 public int synthesizeToFileDescriptor(
1299                         IBinder caller,
1300                         CharSequence text,
1301                         ParcelFileDescriptor fileDescriptor,
1302                         Bundle params,
1303                         String utteranceId) {
1304                     if (!checkNonNull(caller, text, fileDescriptor, params)) {
1305                         return TextToSpeech.ERROR;
1306                     }
1307 
1308                     // In test env, ParcelFileDescriptor instance may be EXACTLY the same
1309                     // one that is used by client. And it will be closed by a client, thus
1310                     // preventing us from writing anything to it.
1311                     final ParcelFileDescriptor sameFileDescriptor =
1312                             ParcelFileDescriptor.adoptFd(fileDescriptor.detachFd());
1313 
1314                     SpeechItem item =
1315                             new SynthesisToFileOutputStreamSpeechItem(
1316                                     caller,
1317                                     Binder.getCallingUid(),
1318                                     Binder.getCallingPid(),
1319                                     params,
1320                                     utteranceId,
1321                                     text,
1322                                     new ParcelFileDescriptor.AutoCloseOutputStream(
1323                                             sameFileDescriptor));
1324                     return mSynthHandler.enqueueSpeechItem(TextToSpeech.QUEUE_ADD, item);
1325                 }
1326 
1327                 @Override
1328                 public int playAudio(
1329                         IBinder caller,
1330                         Uri audioUri,
1331                         int queueMode,
1332                         Bundle params,
1333                         String utteranceId) {
1334                     if (!checkNonNull(caller, audioUri, params)) {
1335                         return TextToSpeech.ERROR;
1336                     }
1337 
1338                     SpeechItem item =
1339                             new AudioSpeechItem(
1340                                     caller,
1341                                     Binder.getCallingUid(),
1342                                     Binder.getCallingPid(),
1343                                     params,
1344                                     utteranceId,
1345                                     audioUri);
1346                     return mSynthHandler.enqueueSpeechItem(queueMode, item);
1347                 }
1348 
1349                 @Override
1350                 public int playSilence(
1351                         IBinder caller, long duration, int queueMode, String utteranceId) {
1352                     if (!checkNonNull(caller)) {
1353                         return TextToSpeech.ERROR;
1354                     }
1355 
1356                     SpeechItem item =
1357                             new SilenceSpeechItem(
1358                                     caller,
1359                                     Binder.getCallingUid(),
1360                                     Binder.getCallingPid(),
1361                                     utteranceId,
1362                                     duration);
1363                     return mSynthHandler.enqueueSpeechItem(queueMode, item);
1364                 }
1365 
1366                 @Override
1367                 public boolean isSpeaking() {
1368                     return mSynthHandler.isSpeaking() || mAudioPlaybackHandler.isSpeaking();
1369                 }
1370 
1371                 @Override
1372                 public int stop(IBinder caller) {
1373                     if (!checkNonNull(caller)) {
1374                         return TextToSpeech.ERROR;
1375                     }
1376 
1377                     return mSynthHandler.stopForApp(caller);
1378                 }
1379 
1380                 @Override
1381                 public String[] getLanguage() {
1382                     return onGetLanguage();
1383                 }
1384 
1385                 @Override
1386                 public String[] getClientDefaultLanguage() {
1387                     return getSettingsLocale();
1388                 }
1389 
1390                 /*
1391                  * If defaults are enforced, then no language is "available" except
1392                  * perhaps the default language selected by the user.
1393                  */
1394                 @Override
1395                 public int isLanguageAvailable(String lang, String country, String variant) {
1396                     if (!checkNonNull(lang)) {
1397                         return TextToSpeech.ERROR;
1398                     }
1399 
1400                     return onIsLanguageAvailable(lang, country, variant);
1401                 }
1402 
1403                 @Override
1404                 public String[] getFeaturesForLanguage(
1405                         String lang, String country, String variant) {
1406                     Set<String> features = onGetFeaturesForLanguage(lang, country, variant);
1407                     String[] featuresArray = null;
1408                     if (features != null) {
1409                         featuresArray = new String[features.size()];
1410                         features.toArray(featuresArray);
1411                     } else {
1412                         featuresArray = new String[0];
1413                     }
1414                     return featuresArray;
1415                 }
1416 
1417                 /*
1418                  * There is no point loading a non default language if defaults
1419                  * are enforced.
1420                  */
1421                 @Override
1422                 public int loadLanguage(
1423                         IBinder caller, String lang, String country, String variant) {
1424                     if (!checkNonNull(lang)) {
1425                         return TextToSpeech.ERROR;
1426                     }
1427                     int retVal = onIsLanguageAvailable(lang, country, variant);
1428 
1429                     if (retVal == TextToSpeech.LANG_AVAILABLE
1430                             || retVal == TextToSpeech.LANG_COUNTRY_AVAILABLE
1431                             || retVal == TextToSpeech.LANG_COUNTRY_VAR_AVAILABLE) {
1432 
1433                         SpeechItem item =
1434                                 new LoadLanguageItem(
1435                                         caller,
1436                                         Binder.getCallingUid(),
1437                                         Binder.getCallingPid(),
1438                                         lang,
1439                                         country,
1440                                         variant);
1441 
1442                         if (mSynthHandler.enqueueSpeechItem(TextToSpeech.QUEUE_ADD, item)
1443                                 != TextToSpeech.SUCCESS) {
1444                             return TextToSpeech.ERROR;
1445                         }
1446                     }
1447                     return retVal;
1448                 }
1449 
1450                 @Override
1451                 public List<Voice> getVoices() {
1452                     return onGetVoices();
1453                 }
1454 
1455                 @Override
1456                 public int loadVoice(IBinder caller, String voiceName) {
1457                     if (!checkNonNull(voiceName)) {
1458                         return TextToSpeech.ERROR;
1459                     }
1460                     int retVal = onIsValidVoiceName(voiceName);
1461 
1462                     if (retVal == TextToSpeech.SUCCESS) {
1463                         SpeechItem item =
1464                                 new LoadVoiceItem(
1465                                         caller,
1466                                         Binder.getCallingUid(),
1467                                         Binder.getCallingPid(),
1468                                         voiceName);
1469                         if (mSynthHandler.enqueueSpeechItem(TextToSpeech.QUEUE_ADD, item)
1470                                 != TextToSpeech.SUCCESS) {
1471                             return TextToSpeech.ERROR;
1472                         }
1473                     }
1474                     return retVal;
1475                 }
1476 
1477                 public String getDefaultVoiceNameFor(String lang, String country, String variant) {
1478                     if (!checkNonNull(lang)) {
1479                         return null;
1480                     }
1481                     int retVal = onIsLanguageAvailable(lang, country, variant);
1482 
1483                     if (retVal == TextToSpeech.LANG_AVAILABLE
1484                             || retVal == TextToSpeech.LANG_COUNTRY_AVAILABLE
1485                             || retVal == TextToSpeech.LANG_COUNTRY_VAR_AVAILABLE) {
1486                         return onGetDefaultVoiceNameFor(lang, country, variant);
1487                     } else {
1488                         return null;
1489                     }
1490                 }
1491 
1492                 @Override
1493                 public void setCallback(IBinder caller, ITextToSpeechCallback cb) {
1494                     // Note that passing in a null callback is a valid use case.
1495                     if (!checkNonNull(caller)) {
1496                         return;
1497                     }
1498 
1499                     mCallbacks.setCallback(caller, cb);
1500                 }
1501 
1502                 private String intern(String in) {
1503                     // The input parameter will be non null.
1504                     return in.intern();
1505                 }
1506 
1507                 private boolean checkNonNull(Object... args) {
1508                     for (Object o : args) {
1509                         if (o == null) return false;
1510                     }
1511                     return true;
1512                 }
1513             };
1514 
1515     private class CallbackMap extends RemoteCallbackList<ITextToSpeechCallback> {
1516         private final HashMap<IBinder, ITextToSpeechCallback> mCallerToCallback
1517                 = new HashMap<IBinder, ITextToSpeechCallback>();
1518 
setCallback(IBinder caller, ITextToSpeechCallback cb)1519         public void setCallback(IBinder caller, ITextToSpeechCallback cb) {
1520             synchronized (mCallerToCallback) {
1521                 ITextToSpeechCallback old;
1522                 if (cb != null) {
1523                     register(cb, caller);
1524                     old = mCallerToCallback.put(caller, cb);
1525                 } else {
1526                     old = mCallerToCallback.remove(caller);
1527                 }
1528                 if (old != null && old != cb) {
1529                     unregister(old);
1530                 }
1531             }
1532         }
1533 
dispatchOnStop(Object callerIdentity, String utteranceId, boolean started)1534         public void dispatchOnStop(Object callerIdentity, String utteranceId, boolean started) {
1535             ITextToSpeechCallback cb = getCallbackFor(callerIdentity);
1536             if (cb == null) return;
1537             try {
1538                 cb.onStop(utteranceId, started);
1539             } catch (RemoteException e) {
1540                 Log.e(TAG, "Callback onStop failed: " + e);
1541             }
1542         }
1543 
dispatchOnSuccess(Object callerIdentity, String utteranceId)1544         public void dispatchOnSuccess(Object callerIdentity, String utteranceId) {
1545             ITextToSpeechCallback cb = getCallbackFor(callerIdentity);
1546             if (cb == null) return;
1547             try {
1548                 cb.onSuccess(utteranceId);
1549             } catch (RemoteException e) {
1550                 Log.e(TAG, "Callback onDone failed: " + e);
1551             }
1552         }
1553 
dispatchOnStart(Object callerIdentity, String utteranceId)1554         public void dispatchOnStart(Object callerIdentity, String utteranceId) {
1555             ITextToSpeechCallback cb = getCallbackFor(callerIdentity);
1556             if (cb == null) return;
1557             try {
1558                 cb.onStart(utteranceId);
1559             } catch (RemoteException e) {
1560                 Log.e(TAG, "Callback onStart failed: " + e);
1561             }
1562         }
1563 
dispatchOnError(Object callerIdentity, String utteranceId, int errorCode)1564         public void dispatchOnError(Object callerIdentity, String utteranceId,
1565                 int errorCode) {
1566             ITextToSpeechCallback cb = getCallbackFor(callerIdentity);
1567             if (cb == null) return;
1568             try {
1569                 cb.onError(utteranceId, errorCode);
1570             } catch (RemoteException e) {
1571                 Log.e(TAG, "Callback onError failed: " + e);
1572             }
1573         }
1574 
dispatchOnBeginSynthesis(Object callerIdentity, String utteranceId, int sampleRateInHz, int audioFormat, int channelCount)1575         public void dispatchOnBeginSynthesis(Object callerIdentity, String utteranceId, int sampleRateInHz, int audioFormat, int channelCount) {
1576             ITextToSpeechCallback cb = getCallbackFor(callerIdentity);
1577             if (cb == null) return;
1578             try {
1579                 cb.onBeginSynthesis(utteranceId, sampleRateInHz, audioFormat, channelCount);
1580             } catch (RemoteException e) {
1581                 Log.e(TAG, "Callback dispatchOnBeginSynthesis(String, int, int, int) failed: " + e);
1582             }
1583         }
1584 
dispatchOnAudioAvailable(Object callerIdentity, String utteranceId, byte[] buffer)1585         public void dispatchOnAudioAvailable(Object callerIdentity, String utteranceId, byte[] buffer) {
1586             ITextToSpeechCallback cb = getCallbackFor(callerIdentity);
1587             if (cb == null) return;
1588             try {
1589                 cb.onAudioAvailable(utteranceId, buffer);
1590             } catch (RemoteException e) {
1591                 Log.e(TAG, "Callback dispatchOnAudioAvailable(String, byte[]) failed: " + e);
1592             }
1593         }
1594 
dispatchOnRangeStart( Object callerIdentity, String utteranceId, int start, int end, int frame)1595         public void dispatchOnRangeStart(
1596                 Object callerIdentity, String utteranceId, int start, int end, int frame) {
1597             ITextToSpeechCallback cb = getCallbackFor(callerIdentity);
1598             if (cb == null) return;
1599             try {
1600                 cb.onRangeStart(utteranceId, start, end, frame);
1601             } catch (RemoteException e) {
1602                 Log.e(TAG, "Callback dispatchOnRangeStart(String, int, int, int) failed: " + e);
1603             }
1604         }
1605 
1606         @Override
onCallbackDied(ITextToSpeechCallback callback, Object cookie)1607         public void onCallbackDied(ITextToSpeechCallback callback, Object cookie) {
1608             IBinder caller = (IBinder) cookie;
1609             synchronized (mCallerToCallback) {
1610                 mCallerToCallback.remove(caller);
1611             }
1612             mSynthHandler.stopForApp(caller);
1613         }
1614 
1615         @Override
kill()1616         public void kill() {
1617             synchronized (mCallerToCallback) {
1618                 mCallerToCallback.clear();
1619                 super.kill();
1620             }
1621         }
1622 
getCallbackFor(Object caller)1623         private ITextToSpeechCallback getCallbackFor(Object caller) {
1624             ITextToSpeechCallback cb;
1625             IBinder asBinder = (IBinder) caller;
1626             synchronized (mCallerToCallback) {
1627                 cb = mCallerToCallback.get(asBinder);
1628             }
1629 
1630             return cb;
1631         }
1632     }
1633 }
1634