TextToSpeechService.java revision 653278341d76d0b23a008087ff94250ae0beb54b
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.FileDescriptor;
38import java.io.FileOutputStream;
39import java.io.IOException;
40import java.util.HashMap;
41import java.util.Locale;
42import java.util.Set;
43
44
45/**
46 * Abstract base class for TTS engine implementations. The following methods
47 * need to be implemented.
48 *
49 * <ul>
50 *   <li>{@link #onIsLanguageAvailable}</li>
51 *   <li>{@link #onLoadLanguage}</li>
52 *   <li>{@link #onGetLanguage}</li>
53 *   <li>{@link #onSynthesizeText}</li>
54 *   <li>{@link #onStop}</li>
55 * </ul>
56 *
57 * The first three deal primarily with language management, and are used to
58 * query the engine for it's support for a given language and indicate to it
59 * that requests in a given language are imminent.
60 *
61 * {@link #onSynthesizeText} is central to the engine implementation. The
62 * implementation should synthesize text as per the request parameters and
63 * return synthesized data via the supplied callback. This class and its helpers
64 * will then consume that data, which might mean queueing it for playback or writing
65 * it to a file or similar. All calls to this method will be on a single
66 * thread, which will be different from the main thread of the service. Synthesis
67 * must be synchronous which means the engine must NOT hold on the callback or call
68 * any methods on it after the method returns
69 *
70 * {@link #onStop} tells the engine that it should stop all ongoing synthesis, if
71 * any. Any pending data from the current synthesis will be discarded.
72 *
73 */
74public abstract class TextToSpeechService extends Service {
75
76    private static final boolean DBG = false;
77    private static final String TAG = "TextToSpeechService";
78
79
80    private static final String SYNTH_THREAD_NAME = "SynthThread";
81
82    private SynthHandler mSynthHandler;
83    // A thread and it's associated handler for playing back any audio
84    // associated with this TTS engine. Will handle all requests except synthesis
85    // to file requests, which occur on the synthesis thread.
86    private AudioPlaybackHandler mAudioPlaybackHandler;
87    private TtsEngines mEngineHelper;
88
89    private CallbackMap mCallbacks;
90    private String mPackageName;
91
92    @Override
93    public void onCreate() {
94        if (DBG) Log.d(TAG, "onCreate()");
95        super.onCreate();
96
97        SynthThread synthThread = new SynthThread();
98        synthThread.start();
99        mSynthHandler = new SynthHandler(synthThread.getLooper());
100
101        mAudioPlaybackHandler = new AudioPlaybackHandler();
102        mAudioPlaybackHandler.start();
103
104        mEngineHelper = new TtsEngines(this);
105
106        mCallbacks = new CallbackMap();
107
108        mPackageName = getApplicationInfo().packageName;
109
110        String[] defaultLocale = getSettingsLocale();
111        // Load default language
112        onLoadLanguage(defaultLocale[0], defaultLocale[1], defaultLocale[2]);
113    }
114
115    @Override
116    public void onDestroy() {
117        if (DBG) Log.d(TAG, "onDestroy()");
118
119        // Tell the synthesizer to stop
120        mSynthHandler.quit();
121        // Tell the audio playback thread to stop.
122        mAudioPlaybackHandler.quit();
123        // Unregister all callbacks.
124        mCallbacks.kill();
125
126        super.onDestroy();
127    }
128
129    /**
130     * Checks whether the engine supports a given language.
131     *
132     * Can be called on multiple threads.
133     *
134     * Its return values HAVE to be consistent with onLoadLanguage.
135     *
136     * @param lang ISO-3 language code.
137     * @param country ISO-3 country code. May be empty or null.
138     * @param variant Language variant. May be empty or null.
139     * @return Code indicating the support status for the locale.
140     *         One of {@link TextToSpeech#LANG_AVAILABLE},
141     *         {@link TextToSpeech#LANG_COUNTRY_AVAILABLE},
142     *         {@link TextToSpeech#LANG_COUNTRY_VAR_AVAILABLE},
143     *         {@link TextToSpeech#LANG_MISSING_DATA}
144     *         {@link TextToSpeech#LANG_NOT_SUPPORTED}.
145     */
146    protected abstract int onIsLanguageAvailable(String lang, String country, String variant);
147
148    /**
149     * Returns the language, country and variant currently being used by the TTS engine.
150     *
151     * Can be called on multiple threads.
152     *
153     * @return A 3-element array, containing language (ISO 3-letter code),
154     *         country (ISO 3-letter code) and variant used by the engine.
155     *         The country and variant may be {@code ""}. If country is empty, then variant must
156     *         be empty too.
157     * @see Locale#getISO3Language()
158     * @see Locale#getISO3Country()
159     * @see Locale#getVariant()
160     */
161    protected abstract String[] onGetLanguage();
162
163    /**
164     * Notifies the engine that it should load a speech synthesis language. There is no guarantee
165     * that this method is always called before the language is used for synthesis. It is merely
166     * a hint to the engine that it will probably get some synthesis requests for this language
167     * at some point in the future.
168     *
169     * Can be called on multiple threads.
170     * In <= Android 4.2 (<= API 17) can be called on main and service binder threads.
171     * In > Android 4.2 (> API 17) can be called on main and synthesis threads.
172     *
173     * @param lang ISO-3 language code.
174     * @param country ISO-3 country code. May be empty or null.
175     * @param variant Language variant. May be empty or null.
176     * @return Code indicating the support status for the locale.
177     *         One of {@link TextToSpeech#LANG_AVAILABLE},
178     *         {@link TextToSpeech#LANG_COUNTRY_AVAILABLE},
179     *         {@link TextToSpeech#LANG_COUNTRY_VAR_AVAILABLE},
180     *         {@link TextToSpeech#LANG_MISSING_DATA}
181     *         {@link TextToSpeech#LANG_NOT_SUPPORTED}.
182     */
183    protected abstract int onLoadLanguage(String lang, String country, String variant);
184
185    /**
186     * Notifies the service that it should stop any in-progress speech synthesis.
187     * This method can be called even if no speech synthesis is currently in progress.
188     *
189     * Can be called on multiple threads, but not on the synthesis thread.
190     */
191    protected abstract void onStop();
192
193    /**
194     * Tells the service to synthesize speech from the given text. This method should
195     * block until the synthesis is finished.
196     *
197     * Called on the synthesis thread.
198     *
199     * @param request The synthesis request.
200     * @param callback The callback the the engine must use to make data available for
201     *         playback or for writing to a file.
202     */
203    protected abstract void onSynthesizeText(SynthesisRequest request,
204            SynthesisCallback callback);
205
206    /**
207     * Queries the service for a set of features supported for a given language.
208     *
209     * @param lang ISO-3 language code.
210     * @param country ISO-3 country code. May be empty or null.
211     * @param variant Language variant. May be empty or null.
212     * @return A list of features supported for the given language.
213     */
214    protected Set<String> onGetFeaturesForLanguage(String lang, String country, String variant) {
215        return null;
216    }
217
218    private int getDefaultSpeechRate() {
219        return getSecureSettingInt(Settings.Secure.TTS_DEFAULT_RATE, Engine.DEFAULT_RATE);
220    }
221
222    private String[] getSettingsLocale() {
223        final String locale = mEngineHelper.getLocalePrefForEngine(mPackageName);
224        return TtsEngines.parseLocalePref(locale);
225    }
226
227    private int getSecureSettingInt(String name, int defaultValue) {
228        return Settings.Secure.getInt(getContentResolver(), name, defaultValue);
229    }
230
231    /**
232     * Synthesizer thread. This thread is used to run {@link SynthHandler}.
233     */
234    private class SynthThread extends HandlerThread implements MessageQueue.IdleHandler {
235
236        private boolean mFirstIdle = true;
237
238        public SynthThread() {
239            super(SYNTH_THREAD_NAME, android.os.Process.THREAD_PRIORITY_DEFAULT);
240        }
241
242        @Override
243        protected void onLooperPrepared() {
244            getLooper().getQueue().addIdleHandler(this);
245        }
246
247        @Override
248        public boolean queueIdle() {
249            if (mFirstIdle) {
250                mFirstIdle = false;
251            } else {
252                broadcastTtsQueueProcessingCompleted();
253            }
254            return true;
255        }
256
257        private void broadcastTtsQueueProcessingCompleted() {
258            Intent i = new Intent(TextToSpeech.ACTION_TTS_QUEUE_PROCESSING_COMPLETED);
259            if (DBG) Log.d(TAG, "Broadcasting: " + i);
260            sendBroadcast(i);
261        }
262    }
263
264    private class SynthHandler extends Handler {
265        private SpeechItem mCurrentSpeechItem = null;
266
267        public SynthHandler(Looper looper) {
268            super(looper);
269        }
270
271        private synchronized SpeechItem getCurrentSpeechItem() {
272            return mCurrentSpeechItem;
273        }
274
275        private synchronized SpeechItem setCurrentSpeechItem(SpeechItem speechItem) {
276            SpeechItem old = mCurrentSpeechItem;
277            mCurrentSpeechItem = speechItem;
278            return old;
279        }
280
281        private synchronized SpeechItem maybeRemoveCurrentSpeechItem(Object callerIdentity) {
282            if (mCurrentSpeechItem != null &&
283                    (mCurrentSpeechItem.getCallerIdentity() == callerIdentity)) {
284                SpeechItem current = mCurrentSpeechItem;
285                mCurrentSpeechItem = null;
286                return current;
287            }
288
289            return null;
290        }
291
292        public boolean isSpeaking() {
293            return getCurrentSpeechItem() != null;
294        }
295
296        public void quit() {
297            // Don't process any more speech items
298            getLooper().quit();
299            // Stop the current speech item
300            SpeechItem current = setCurrentSpeechItem(null);
301            if (current != null) {
302                current.stop();
303            }
304            // The AudioPlaybackHandler will be destroyed by the caller.
305        }
306
307        /**
308         * Adds a speech item to the queue.
309         *
310         * Called on a service binder thread.
311         */
312        public int enqueueSpeechItem(int queueMode, final SpeechItem speechItem) {
313            UtteranceProgressDispatcher utterenceProgress = null;
314            if (speechItem instanceof UtteranceProgressDispatcher) {
315                utterenceProgress = (UtteranceProgressDispatcher) speechItem;
316            }
317
318            if (!speechItem.isValid()) {
319                if (utterenceProgress != null) {
320                    utterenceProgress.dispatchOnError();
321                }
322                return TextToSpeech.ERROR;
323            }
324
325            if (queueMode == TextToSpeech.QUEUE_FLUSH) {
326                stopForApp(speechItem.getCallerIdentity());
327            } else if (queueMode == TextToSpeech.QUEUE_DESTROY) {
328                stopAll();
329            }
330            Runnable runnable = new Runnable() {
331                @Override
332                public void run() {
333                    setCurrentSpeechItem(speechItem);
334                    speechItem.play();
335                    setCurrentSpeechItem(null);
336                }
337            };
338            Message msg = Message.obtain(this, runnable);
339
340            // The obj is used to remove all callbacks from the given app in
341            // stopForApp(String).
342            //
343            // Note that this string is interned, so the == comparison works.
344            msg.obj = speechItem.getCallerIdentity();
345            if (sendMessage(msg)) {
346                return TextToSpeech.SUCCESS;
347            } else {
348                Log.w(TAG, "SynthThread has quit");
349                if (utterenceProgress != null) {
350                    utterenceProgress.dispatchOnError();
351                }
352                return TextToSpeech.ERROR;
353            }
354        }
355
356        /**
357         * Stops all speech output and removes any utterances still in the queue for
358         * the calling app.
359         *
360         * Called on a service binder thread.
361         */
362        public int stopForApp(Object callerIdentity) {
363            if (callerIdentity == null) {
364                return TextToSpeech.ERROR;
365            }
366
367            removeCallbacksAndMessages(callerIdentity);
368            // This stops writing data to the file / or publishing
369            // items to the audio playback handler.
370            //
371            // Note that the current speech item must be removed only if it
372            // belongs to the callingApp, else the item will be "orphaned" and
373            // not stopped correctly if a stop request comes along for the item
374            // from the app it belongs to.
375            SpeechItem current = maybeRemoveCurrentSpeechItem(callerIdentity);
376            if (current != null) {
377                current.stop();
378            }
379
380            // Remove any enqueued audio too.
381            mAudioPlaybackHandler.stopForApp(callerIdentity);
382
383            return TextToSpeech.SUCCESS;
384        }
385
386        public int stopAll() {
387            // Stop the current speech item unconditionally .
388            SpeechItem current = setCurrentSpeechItem(null);
389            if (current != null) {
390                current.stop();
391            }
392            // Remove all other items from the queue.
393            removeCallbacksAndMessages(null);
394            // Remove all pending playback as well.
395            mAudioPlaybackHandler.stop();
396
397            return TextToSpeech.SUCCESS;
398        }
399    }
400
401    interface UtteranceProgressDispatcher {
402        public void dispatchOnDone();
403        public void dispatchOnStart();
404        public void dispatchOnError();
405    }
406
407    /**
408     * An item in the synth thread queue.
409     */
410    private abstract class SpeechItem {
411        private final Object mCallerIdentity;
412        protected final Bundle mParams;
413        private final int mCallerUid;
414        private final int mCallerPid;
415        private boolean mStarted = false;
416        private boolean mStopped = false;
417
418        public SpeechItem(Object caller, int callerUid, int callerPid, Bundle params) {
419            mCallerIdentity = caller;
420            mParams = params;
421            mCallerUid = callerUid;
422            mCallerPid = callerPid;
423        }
424
425        public Object getCallerIdentity() {
426            return mCallerIdentity;
427        }
428
429
430        public int getCallerUid() {
431            return mCallerUid;
432        }
433
434        public int getCallerPid() {
435            return mCallerPid;
436        }
437
438        /**
439         * Checker whether the item is valid. If this method returns false, the item should not
440         * be played.
441         */
442        public abstract boolean isValid();
443
444        /**
445         * Plays the speech item. Blocks until playback is finished.
446         * Must not be called more than once.
447         *
448         * Only called on the synthesis thread.
449         *
450         * @return {@link TextToSpeech#SUCCESS} or {@link TextToSpeech#ERROR}.
451         */
452        public int play() {
453            synchronized (this) {
454                if (mStarted) {
455                    throw new IllegalStateException("play() called twice");
456                }
457                mStarted = true;
458            }
459            return playImpl();
460        }
461
462        protected abstract int playImpl();
463
464        /**
465         * Stops the speech item.
466         * Must not be called more than once.
467         *
468         * Can be called on multiple threads,  but not on the synthesis thread.
469         */
470        public void stop() {
471            synchronized (this) {
472                if (mStopped) {
473                    throw new IllegalStateException("stop() called twice");
474                }
475                mStopped = true;
476            }
477            stopImpl();
478        }
479
480        protected abstract void stopImpl();
481
482        protected synchronized boolean isStopped() {
483             return mStopped;
484        }
485    }
486
487    /**
488     * An item in the synth thread queue that process utterance.
489     */
490    private abstract class UtteranceSpeechItem extends SpeechItem
491        implements UtteranceProgressDispatcher  {
492
493        public UtteranceSpeechItem(Object caller, int callerUid, int callerPid, Bundle params) {
494            super(caller, callerUid, callerPid, params);
495        }
496
497        @Override
498        public void dispatchOnDone() {
499            final String utteranceId = getUtteranceId();
500            if (utteranceId != null) {
501                mCallbacks.dispatchOnDone(getCallerIdentity(), utteranceId);
502            }
503        }
504
505        @Override
506        public void dispatchOnStart() {
507            final String utteranceId = getUtteranceId();
508            if (utteranceId != null) {
509                mCallbacks.dispatchOnStart(getCallerIdentity(), utteranceId);
510            }
511        }
512
513        @Override
514        public void dispatchOnError() {
515            final String utteranceId = getUtteranceId();
516            if (utteranceId != null) {
517                mCallbacks.dispatchOnError(getCallerIdentity(), utteranceId);
518            }
519        }
520
521        public int getStreamType() {
522            return getIntParam(Engine.KEY_PARAM_STREAM, Engine.DEFAULT_STREAM);
523        }
524
525        public float getVolume() {
526            return getFloatParam(Engine.KEY_PARAM_VOLUME, Engine.DEFAULT_VOLUME);
527        }
528
529        public float getPan() {
530            return getFloatParam(Engine.KEY_PARAM_PAN, Engine.DEFAULT_PAN);
531        }
532
533        public String getUtteranceId() {
534            return getStringParam(Engine.KEY_PARAM_UTTERANCE_ID, null);
535        }
536
537        protected String getStringParam(String key, String defaultValue) {
538            return mParams == null ? defaultValue : mParams.getString(key, defaultValue);
539        }
540
541        protected int getIntParam(String key, int defaultValue) {
542            return mParams == null ? defaultValue : mParams.getInt(key, defaultValue);
543        }
544
545        protected float getFloatParam(String key, float defaultValue) {
546            return mParams == null ? defaultValue : mParams.getFloat(key, defaultValue);
547        }
548
549    }
550
551    class SynthesisSpeechItem extends UtteranceSpeechItem {
552        // Never null.
553        private final String mText;
554        private final SynthesisRequest mSynthesisRequest;
555        private final String[] mDefaultLocale;
556        // Non null after synthesis has started, and all accesses
557        // guarded by 'this'.
558        private AbstractSynthesisCallback mSynthesisCallback;
559        private final EventLogger mEventLogger;
560        private final int mCallerUid;
561
562        public SynthesisSpeechItem(Object callerIdentity, int callerUid, int callerPid,
563                Bundle params, String text) {
564            super(callerIdentity, callerUid, callerPid, params);
565            mText = text;
566            mCallerUid = callerUid;
567            mSynthesisRequest = new SynthesisRequest(mText, mParams);
568            mDefaultLocale = getSettingsLocale();
569            setRequestParams(mSynthesisRequest);
570            mEventLogger = new EventLogger(mSynthesisRequest, callerUid, callerPid,
571                    mPackageName);
572        }
573
574        public String getText() {
575            return mText;
576        }
577
578        @Override
579        public boolean isValid() {
580            if (mText == null) {
581                Log.e(TAG, "null synthesis text");
582                return false;
583            }
584            if (mText.length() >= TextToSpeech.getMaxSpeechInputLength()) {
585                Log.w(TAG, "Text too long: " + mText.length() + " chars");
586                return false;
587            }
588            return true;
589        }
590
591        @Override
592        protected int playImpl() {
593            AbstractSynthesisCallback synthesisCallback;
594            mEventLogger.onRequestProcessingStart();
595            synchronized (this) {
596                // stop() might have been called before we enter this
597                // synchronized block.
598                if (isStopped()) {
599                    return TextToSpeech.ERROR;
600                }
601                mSynthesisCallback = createSynthesisCallback();
602                synthesisCallback = mSynthesisCallback;
603            }
604            TextToSpeechService.this.onSynthesizeText(mSynthesisRequest, synthesisCallback);
605            return synthesisCallback.isDone() ? TextToSpeech.SUCCESS : TextToSpeech.ERROR;
606        }
607
608        protected AbstractSynthesisCallback createSynthesisCallback() {
609            return new PlaybackSynthesisCallback(getStreamType(), getVolume(), getPan(),
610                    mAudioPlaybackHandler, this, getCallerIdentity(), mEventLogger);
611        }
612
613        private void setRequestParams(SynthesisRequest request) {
614            request.setLanguage(getLanguage(), getCountry(), getVariant());
615            request.setSpeechRate(getSpeechRate());
616            request.setCallerUid(mCallerUid);
617            request.setPitch(getPitch());
618        }
619
620        @Override
621        protected void stopImpl() {
622            AbstractSynthesisCallback synthesisCallback;
623            synchronized (this) {
624                synthesisCallback = mSynthesisCallback;
625            }
626            if (synthesisCallback != null) {
627                // If the synthesis callback is null, it implies that we haven't
628                // entered the synchronized(this) block in playImpl which in
629                // turn implies that synthesis would not have started.
630                synthesisCallback.stop();
631                TextToSpeechService.this.onStop();
632            }
633        }
634
635        public String getLanguage() {
636            return getStringParam(Engine.KEY_PARAM_LANGUAGE, mDefaultLocale[0]);
637        }
638
639        private boolean hasLanguage() {
640            return !TextUtils.isEmpty(getStringParam(Engine.KEY_PARAM_LANGUAGE, null));
641        }
642
643        private String getCountry() {
644            if (!hasLanguage()) return mDefaultLocale[1];
645            return getStringParam(Engine.KEY_PARAM_COUNTRY, "");
646        }
647
648        private String getVariant() {
649            if (!hasLanguage()) return mDefaultLocale[2];
650            return getStringParam(Engine.KEY_PARAM_VARIANT, "");
651        }
652
653        private int getSpeechRate() {
654            return getIntParam(Engine.KEY_PARAM_RATE, getDefaultSpeechRate());
655        }
656
657        private int getPitch() {
658            return getIntParam(Engine.KEY_PARAM_PITCH, Engine.DEFAULT_PITCH);
659        }
660    }
661
662    private class SynthesisToFileOutputStreamSpeechItem extends SynthesisSpeechItem {
663        private final FileOutputStream mFileOutputStream;
664
665        public SynthesisToFileOutputStreamSpeechItem(Object callerIdentity, int callerUid,
666                int callerPid, Bundle params, String text, FileOutputStream fileOutputStream) {
667            super(callerIdentity, callerUid, callerPid, params, text);
668            mFileOutputStream = fileOutputStream;
669        }
670
671        @Override
672        protected AbstractSynthesisCallback createSynthesisCallback() {
673            return new FileSynthesisCallback(mFileOutputStream.getChannel());
674        }
675
676        @Override
677        protected int playImpl() {
678            dispatchOnStart();
679            int status = super.playImpl();
680            if (status == TextToSpeech.SUCCESS) {
681                dispatchOnDone();
682            } else {
683                dispatchOnError();
684            }
685            try {
686              mFileOutputStream.close();
687            } catch(IOException e) {
688              Log.w(TAG, "Failed to close output file", e);
689            }
690            return status;
691        }
692    }
693
694    private class AudioSpeechItem extends UtteranceSpeechItem {
695        private final AudioPlaybackQueueItem mItem;
696        public AudioSpeechItem(Object callerIdentity, int callerUid, int callerPid,
697                Bundle params, Uri uri) {
698            super(callerIdentity, callerUid, callerPid, params);
699            mItem = new AudioPlaybackQueueItem(this, getCallerIdentity(),
700                    TextToSpeechService.this, uri, getStreamType());
701        }
702
703        @Override
704        public boolean isValid() {
705            return true;
706        }
707
708        @Override
709        protected int playImpl() {
710            mAudioPlaybackHandler.enqueue(mItem);
711            return TextToSpeech.SUCCESS;
712        }
713
714        @Override
715        protected void stopImpl() {
716            // Do nothing.
717        }
718    }
719
720    private class SilenceSpeechItem extends UtteranceSpeechItem {
721        private final long mDuration;
722
723        public SilenceSpeechItem(Object callerIdentity, int callerUid, int callerPid,
724                Bundle params, long duration) {
725            super(callerIdentity, callerUid, callerPid, params);
726            mDuration = duration;
727        }
728
729        @Override
730        public boolean isValid() {
731            return true;
732        }
733
734        @Override
735        protected int playImpl() {
736            mAudioPlaybackHandler.enqueue(new SilencePlaybackQueueItem(
737                    this, getCallerIdentity(), mDuration));
738            return TextToSpeech.SUCCESS;
739        }
740
741        @Override
742        protected void stopImpl() {
743            // Do nothing, handled by AudioPlaybackHandler#stopForApp
744        }
745    }
746
747    private class LoadLanguageItem extends SpeechItem {
748        private final String mLanguage;
749        private final String mCountry;
750        private final String mVariant;
751
752        public LoadLanguageItem(Object callerIdentity, int callerUid, int callerPid,
753                Bundle params, String language, String country, String variant) {
754            super(callerIdentity, callerUid, callerPid, params);
755            mLanguage = language;
756            mCountry = country;
757            mVariant = variant;
758        }
759
760        @Override
761        public boolean isValid() {
762            return true;
763        }
764
765        @Override
766        protected int playImpl() {
767            int result = TextToSpeechService.this.onLoadLanguage(mLanguage, mCountry, mVariant);
768            if (result == TextToSpeech.LANG_AVAILABLE ||
769                    result == TextToSpeech.LANG_COUNTRY_AVAILABLE ||
770                    result == TextToSpeech.LANG_COUNTRY_VAR_AVAILABLE) {
771                return TextToSpeech.SUCCESS;
772            }
773            return TextToSpeech.ERROR;
774        }
775
776        @Override
777        protected void stopImpl() {
778            // No-op
779        }
780    }
781
782    @Override
783    public IBinder onBind(Intent intent) {
784        if (TextToSpeech.Engine.INTENT_ACTION_TTS_SERVICE.equals(intent.getAction())) {
785            return mBinder;
786        }
787        return null;
788    }
789
790    /**
791     * Binder returned from {@code #onBind(Intent)}. The methods in this class can be
792     * called called from several different threads.
793     */
794    // NOTE: All calls that are passed in a calling app are interned so that
795    // they can be used as message objects (which are tested for equality using ==).
796    private final ITextToSpeechService.Stub mBinder = new ITextToSpeechService.Stub() {
797        @Override
798        public int speak(IBinder caller, String text, int queueMode, Bundle params) {
799            if (!checkNonNull(caller, text, params)) {
800                return TextToSpeech.ERROR;
801            }
802
803            SpeechItem item = new SynthesisSpeechItem(caller,
804                    Binder.getCallingUid(), Binder.getCallingPid(), params, text);
805            return mSynthHandler.enqueueSpeechItem(queueMode, item);
806        }
807
808        @Override
809        public int synthesizeToFileDescriptor(IBinder caller, String text, ParcelFileDescriptor
810                fileDescriptor, Bundle params) {
811            if (!checkNonNull(caller, text, fileDescriptor, params)) {
812                return TextToSpeech.ERROR;
813            }
814
815            // In test env, ParcelFileDescriptor instance may be EXACTLY the same
816            // one that is used by client. And it will be closed by a client, thus
817            // preventing us from writing anything to it.
818            final ParcelFileDescriptor sameFileDescriptor = ParcelFileDescriptor.adoptFd(
819                    fileDescriptor.detachFd());
820
821            SpeechItem item = new SynthesisToFileOutputStreamSpeechItem(caller,
822                    Binder.getCallingUid(), Binder.getCallingPid(), params, text,
823                    new ParcelFileDescriptor.AutoCloseOutputStream(sameFileDescriptor));
824            return mSynthHandler.enqueueSpeechItem(TextToSpeech.QUEUE_ADD, item);
825        }
826
827        @Override
828        public int playAudio(IBinder caller, Uri audioUri, int queueMode, Bundle params) {
829            if (!checkNonNull(caller, audioUri, params)) {
830                return TextToSpeech.ERROR;
831            }
832
833            SpeechItem item = new AudioSpeechItem(caller,
834                    Binder.getCallingUid(), Binder.getCallingPid(), params, audioUri);
835            return mSynthHandler.enqueueSpeechItem(queueMode, item);
836        }
837
838        @Override
839        public int playSilence(IBinder caller, long duration, int queueMode, Bundle params) {
840            if (!checkNonNull(caller, params)) {
841                return TextToSpeech.ERROR;
842            }
843
844            SpeechItem item = new SilenceSpeechItem(caller,
845                    Binder.getCallingUid(), Binder.getCallingPid(), params, duration);
846            return mSynthHandler.enqueueSpeechItem(queueMode, item);
847        }
848
849        @Override
850        public boolean isSpeaking() {
851            return mSynthHandler.isSpeaking() || mAudioPlaybackHandler.isSpeaking();
852        }
853
854        @Override
855        public int stop(IBinder caller) {
856            if (!checkNonNull(caller)) {
857                return TextToSpeech.ERROR;
858            }
859
860            return mSynthHandler.stopForApp(caller);
861        }
862
863        @Override
864        public String[] getLanguage() {
865            return onGetLanguage();
866        }
867
868        @Override
869        public String[] getClientDefaultLanguage() {
870            return getSettingsLocale();
871        }
872
873        /*
874         * If defaults are enforced, then no language is "available" except
875         * perhaps the default language selected by the user.
876         */
877        @Override
878        public int isLanguageAvailable(String lang, String country, String variant) {
879            if (!checkNonNull(lang)) {
880                return TextToSpeech.ERROR;
881            }
882
883            return onIsLanguageAvailable(lang, country, variant);
884        }
885
886        @Override
887        public String[] getFeaturesForLanguage(String lang, String country, String variant) {
888            Set<String> features = onGetFeaturesForLanguage(lang, country, variant);
889            String[] featuresArray = null;
890            if (features != null) {
891                featuresArray = new String[features.size()];
892                features.toArray(featuresArray);
893            } else {
894                featuresArray = new String[0];
895            }
896            return featuresArray;
897        }
898
899        /*
900         * There is no point loading a non default language if defaults
901         * are enforced.
902         */
903        @Override
904        public int loadLanguage(IBinder caller, String lang, String country, String variant) {
905            if (!checkNonNull(lang)) {
906                return TextToSpeech.ERROR;
907            }
908            int retVal = onIsLanguageAvailable(lang, country, variant);
909
910            if (retVal == TextToSpeech.LANG_AVAILABLE ||
911                    retVal == TextToSpeech.LANG_COUNTRY_AVAILABLE ||
912                    retVal == TextToSpeech.LANG_COUNTRY_VAR_AVAILABLE) {
913
914                SpeechItem item = new LoadLanguageItem(caller, Binder.getCallingUid(),
915                    Binder.getCallingPid(), null, lang, country, variant);
916
917                if (mSynthHandler.enqueueSpeechItem(TextToSpeech.QUEUE_ADD, item) !=
918                        TextToSpeech.SUCCESS) {
919                    return TextToSpeech.ERROR;
920                }
921            }
922            return retVal;
923        }
924
925        @Override
926        public void setCallback(IBinder caller, ITextToSpeechCallback cb) {
927            // Note that passing in a null callback is a valid use case.
928            if (!checkNonNull(caller)) {
929                return;
930            }
931
932            mCallbacks.setCallback(caller, cb);
933        }
934
935        private String intern(String in) {
936            // The input parameter will be non null.
937            return in.intern();
938        }
939
940        private boolean checkNonNull(Object... args) {
941            for (Object o : args) {
942                if (o == null) return false;
943            }
944            return true;
945        }
946    };
947
948    private class CallbackMap extends RemoteCallbackList<ITextToSpeechCallback> {
949        private final HashMap<IBinder, ITextToSpeechCallback> mCallerToCallback
950                = new HashMap<IBinder, ITextToSpeechCallback>();
951
952        public void setCallback(IBinder caller, ITextToSpeechCallback cb) {
953            synchronized (mCallerToCallback) {
954                ITextToSpeechCallback old;
955                if (cb != null) {
956                    register(cb, caller);
957                    old = mCallerToCallback.put(caller, cb);
958                } else {
959                    old = mCallerToCallback.remove(caller);
960                }
961                if (old != null && old != cb) {
962                    unregister(old);
963                }
964            }
965        }
966
967        public void dispatchOnDone(Object callerIdentity, String utteranceId) {
968            ITextToSpeechCallback cb = getCallbackFor(callerIdentity);
969            if (cb == null) return;
970            try {
971                cb.onDone(utteranceId);
972            } catch (RemoteException e) {
973                Log.e(TAG, "Callback onDone failed: " + e);
974            }
975        }
976
977        public void dispatchOnStart(Object callerIdentity, String utteranceId) {
978            ITextToSpeechCallback cb = getCallbackFor(callerIdentity);
979            if (cb == null) return;
980            try {
981                cb.onStart(utteranceId);
982            } catch (RemoteException e) {
983                Log.e(TAG, "Callback onStart failed: " + e);
984            }
985
986        }
987
988        public void dispatchOnError(Object callerIdentity, String utteranceId) {
989            ITextToSpeechCallback cb = getCallbackFor(callerIdentity);
990            if (cb == null) return;
991            try {
992                cb.onError(utteranceId);
993            } catch (RemoteException e) {
994                Log.e(TAG, "Callback onError failed: " + e);
995            }
996        }
997
998        @Override
999        public void onCallbackDied(ITextToSpeechCallback callback, Object cookie) {
1000            IBinder caller = (IBinder) cookie;
1001            synchronized (mCallerToCallback) {
1002                mCallerToCallback.remove(caller);
1003            }
1004            mSynthHandler.stopForApp(caller);
1005        }
1006
1007        @Override
1008        public void kill() {
1009            synchronized (mCallerToCallback) {
1010                mCallerToCallback.clear();
1011                super.kill();
1012            }
1013        }
1014
1015        private ITextToSpeechCallback getCallbackFor(Object caller) {
1016            ITextToSpeechCallback cb;
1017            IBinder asBinder = (IBinder) caller;
1018            synchronized (mCallerToCallback) {
1019                cb = mCallerToCallback.get(asBinder);
1020            }
1021
1022            return cb;
1023        }
1024
1025    }
1026
1027}
1028