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