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