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