TtsService.java revision a9c5e4bf2639f8f09be8bace4230613b7b689f0e
1/*
2 * Copyright (C) 2009 Google Inc.
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.tts;
17
18import android.app.Service;
19import android.content.ContentResolver;
20import android.content.Context;
21import android.content.Intent;
22import android.content.SharedPreferences;
23import android.content.pm.PackageManager;
24import android.content.pm.PackageManager.NameNotFoundException;
25import android.media.MediaPlayer;
26import android.media.MediaPlayer.OnCompletionListener;
27import android.net.Uri;
28import android.os.IBinder;
29import android.os.RemoteCallbackList;
30import android.os.RemoteException;
31import android.preference.PreferenceManager;
32import android.speech.tts.ITts.Stub;
33import android.speech.tts.ITtsCallback;
34import android.speech.tts.TextToSpeech;
35import android.util.Log;
36import java.util.ArrayList;
37import java.util.Arrays;
38import java.util.HashMap;
39import java.util.Locale;
40import java.util.concurrent.locks.ReentrantLock;
41import java.util.concurrent.TimeUnit;
42
43
44/**
45 * @hide Synthesizes speech from text. This is implemented as a service so that
46 *       other applications can call the TTS without needing to bundle the TTS
47 *       in the build.
48 *
49 */
50public class TtsService extends Service implements OnCompletionListener {
51
52    private static class SpeechItem {
53        public static final int TEXT = 0;
54        public static final int EARCON = 1;
55        public static final int SILENCE = 2;
56        public static final int TEXT_TO_FILE = 3;
57        public String mText = "";
58        public ArrayList<String> mParams = null;
59        public int mType = TEXT;
60        public long mDuration = 0;
61        public String mFilename = null;
62        public String callingApp = "";
63
64        public SpeechItem(String source, String text, ArrayList<String> params, int itemType) {
65            mText = text;
66            mParams = params;
67            mType = itemType;
68            callingApp = source;
69        }
70
71        public SpeechItem(String source, long silenceTime) {
72            mDuration = silenceTime;
73            mType = SILENCE;
74            callingApp = source;
75        }
76
77        public SpeechItem(String source, String text, ArrayList<String> params, int itemType, String filename) {
78            mText = text;
79            mParams = params;
80            mType = itemType;
81            mFilename = filename;
82            callingApp = source;
83        }
84
85    }
86
87    /**
88     * Contains the information needed to access a sound resource; the name of
89     * the package that contains the resource and the resID of the resource
90     * within that package.
91     */
92    private static class SoundResource {
93        public String mSourcePackageName = null;
94        public int mResId = -1;
95        public String mFilename = null;
96
97        public SoundResource(String packageName, int id) {
98            mSourcePackageName = packageName;
99            mResId = id;
100            mFilename = null;
101        }
102
103        public SoundResource(String file) {
104            mSourcePackageName = null;
105            mResId = -1;
106            mFilename = file;
107        }
108    }
109
110    private static final int MAX_SPEECH_ITEM_CHAR_LENGTH = 4000;
111    private static final int MAX_FILENAME_LENGTH = 250;
112
113    private static final String ACTION = "android.intent.action.START_TTS_SERVICE";
114    private static final String CATEGORY = "android.intent.category.TTS";
115    private static final String PKGNAME = "android.tts";
116
117    final RemoteCallbackList<android.speech.tts.ITtsCallback> mCallbacks = new RemoteCallbackList<ITtsCallback>();
118
119    private Boolean mIsSpeaking;
120    private ArrayList<SpeechItem> mSpeechQueue;
121    private HashMap<String, SoundResource> mEarcons;
122    private HashMap<String, SoundResource> mUtterances;
123    private MediaPlayer mPlayer;
124    private TtsService mSelf;
125
126    private ContentResolver mResolver;
127
128    private final ReentrantLock speechQueueLock = new ReentrantLock();
129    private final ReentrantLock synthesizerLock = new ReentrantLock();
130
131    private SynthProxy nativeSynth;
132    @Override
133    public void onCreate() {
134        super.onCreate();
135        //Log.i("TTS", "TTS starting");
136
137        mResolver = getContentResolver();
138
139        String soLibPath = "/system/lib/libttspico.so";
140        nativeSynth = new SynthProxy(soLibPath);
141
142        mSelf = this;
143        mIsSpeaking = false;
144
145        mEarcons = new HashMap<String, SoundResource>();
146        mUtterances = new HashMap<String, SoundResource>();
147
148        mSpeechQueue = new ArrayList<SpeechItem>();
149        mPlayer = null;
150
151        setDefaultSettings();
152    }
153
154    @Override
155    public void onDestroy() {
156        super.onDestroy();
157        // Don't hog the media player
158        cleanUpPlayer();
159
160        nativeSynth.shutdown();
161
162        // Unregister all callbacks.
163        mCallbacks.kill();
164    }
165
166
167    private void setDefaultSettings() {
168        setLanguage("", this.getDefaultLanguage(), getDefaultCountry(), getDefaultLocVariant());
169
170        // speech rate
171        setSpeechRate("", getDefaultRate());
172    }
173
174
175    private boolean isDefaultEnforced() {
176        return (android.provider.Settings.Secure.getInt(mResolver,
177                    android.provider.Settings.Secure.TTS_USE_DEFAULTS,
178                    TextToSpeech.Engine.FALLBACK_TTS_USE_DEFAULTS)
179                == 1 );
180    }
181
182
183    private int getDefaultRate() {
184        return android.provider.Settings.Secure.getInt(mResolver,
185                android.provider.Settings.Secure.TTS_DEFAULT_RATE,
186                TextToSpeech.Engine.FALLBACK_TTS_DEFAULT_RATE);
187    }
188
189
190    private String getDefaultLanguage() {
191        String defaultLang = android.provider.Settings.Secure.getString(mResolver,
192                android.provider.Settings.Secure.TTS_DEFAULT_LANG);
193        if (defaultLang == null) {
194            // no setting found, use the current Locale to determine the default language
195            return Locale.getDefault().getISO3Language();
196        } else {
197            return defaultLang;
198        }
199    }
200
201
202    private String getDefaultCountry() {
203        String defaultCountry = android.provider.Settings.Secure.getString(mResolver,
204                android.provider.Settings.Secure.TTS_DEFAULT_COUNTRY);
205        if (defaultCountry == null) {
206            // no setting found, use the current Locale to determine the default country
207            return Locale.getDefault().getISO3Country();
208        } else {
209            return defaultCountry;
210        }
211    }
212
213
214    private String getDefaultLocVariant() {
215        String defaultVar = android.provider.Settings.Secure.getString(mResolver,
216                android.provider.Settings.Secure.TTS_DEFAULT_VARIANT);
217        if (defaultVar == null) {
218            // no setting found, use the current Locale to determine the default variant
219            return Locale.getDefault().getVariant();
220        } else {
221            return defaultVar;
222        }
223    }
224
225
226    private int setSpeechRate(String callingApp, int rate) {
227        if (isDefaultEnforced()) {
228            return nativeSynth.setSpeechRate(getDefaultRate());
229        } else {
230            return nativeSynth.setSpeechRate(rate);
231        }
232    }
233
234
235    private int setPitch(String callingApp, int pitch) {
236        return nativeSynth.setPitch(pitch);
237    }
238
239
240    private int isLanguageAvailable(String lang, String country, String variant) {
241        //Log.v("TTS", "TtsService.isLanguageAvailable(" + lang + ", " + country + ", " +variant+")");
242        return nativeSynth.isLanguageAvailable(lang, country, variant);
243    }
244
245
246    private String[] getLanguage() {
247        return nativeSynth.getLanguage();
248    }
249
250
251    private int setLanguage(String callingApp, String lang, String country, String variant) {
252        //Log.v("TTS", "TtsService.setLanguage(" + lang + ", " + country + ", " + variant + ")");
253        if (isDefaultEnforced()) {
254            return nativeSynth.setLanguage(getDefaultLanguage(), getDefaultCountry(),
255                    getDefaultLocVariant());
256        } else {
257            return nativeSynth.setLanguage(lang, country, variant);
258        }
259    }
260
261
262    /**
263     * Adds a sound resource to the TTS.
264     *
265     * @param text
266     *            The text that should be associated with the sound resource
267     * @param packageName
268     *            The name of the package which has the sound resource
269     * @param resId
270     *            The resource ID of the sound within its package
271     */
272    private void addSpeech(String callingApp, String text, String packageName, int resId) {
273        mUtterances.put(text, new SoundResource(packageName, resId));
274    }
275
276    /**
277     * Adds a sound resource to the TTS.
278     *
279     * @param text
280     *            The text that should be associated with the sound resource
281     * @param filename
282     *            The filename of the sound resource. This must be a complete
283     *            path like: (/sdcard/mysounds/mysoundbite.mp3).
284     */
285    private void addSpeech(String callingApp, String text, String filename) {
286        mUtterances.put(text, new SoundResource(filename));
287    }
288
289    /**
290     * Adds a sound resource to the TTS as an earcon.
291     *
292     * @param earcon
293     *            The text that should be associated with the sound resource
294     * @param packageName
295     *            The name of the package which has the sound resource
296     * @param resId
297     *            The resource ID of the sound within its package
298     */
299    private void addEarcon(String callingApp, String earcon, String packageName, int resId) {
300        mEarcons.put(earcon, new SoundResource(packageName, resId));
301    }
302
303    /**
304     * Adds a sound resource to the TTS as an earcon.
305     *
306     * @param earcon
307     *            The text that should be associated with the sound resource
308     * @param filename
309     *            The filename of the sound resource. This must be a complete
310     *            path like: (/sdcard/mysounds/mysoundbite.mp3).
311     */
312    private void addEarcon(String callingApp, String earcon, String filename) {
313        mEarcons.put(earcon, new SoundResource(filename));
314    }
315
316    /**
317     * Speaks the given text using the specified queueing mode and parameters.
318     *
319     * @param text
320     *            The text that should be spoken
321     * @param queueMode
322     *            0 for no queue (interrupts all previous utterances), 1 for
323     *            queued
324     * @param params
325     *            An ArrayList of parameters. This is not implemented for all
326     *            engines.
327     */
328    private int speak(String callingApp, String text, int queueMode, ArrayList<String> params) {
329        if (queueMode == 0) {
330            stop(callingApp);
331        }
332        mSpeechQueue.add(new SpeechItem(callingApp, text, params, SpeechItem.TEXT));
333        if (!mIsSpeaking) {
334            processSpeechQueue();
335        }
336        return TextToSpeech.TTS_SUCCESS;
337    }
338
339    /**
340     * Plays the earcon using the specified queueing mode and parameters.
341     *
342     * @param earcon
343     *            The earcon that should be played
344     * @param queueMode
345     *            0 for no queue (interrupts all previous utterances), 1 for
346     *            queued
347     * @param params
348     *            An ArrayList of parameters. This is not implemented for all
349     *            engines.
350     */
351    private int playEarcon(String callingApp, String earcon, int queueMode,
352            ArrayList<String> params) {
353        if (queueMode == 0) {
354            stop(callingApp);
355        }
356        mSpeechQueue.add(new SpeechItem(callingApp, earcon, params, SpeechItem.EARCON));
357        if (!mIsSpeaking) {
358            processSpeechQueue();
359        }
360        return TextToSpeech.TTS_SUCCESS;
361    }
362
363    /**
364     * Stops all speech output and removes any utterances still in the queue.
365     */
366    private int stop(String callingApp) {
367        int result = TextToSpeech.TTS_ERROR;
368        boolean speechQueueAvailable = false;
369        try{
370            // If the queue is locked for more than 1 second,
371            // something has gone very wrong with processSpeechQueue.
372            speechQueueAvailable = speechQueueLock.tryLock(1000, TimeUnit.MILLISECONDS);
373            if (speechQueueAvailable) {
374                Log.i("TTS", "Stopping");
375                for (int i = mSpeechQueue.size() - 1; i > -1; i--){
376                    if (mSpeechQueue.get(i).callingApp.equals(callingApp)){
377                        mSpeechQueue.remove(i);
378                    }
379                }
380
381                result = nativeSynth.stop();
382                mIsSpeaking = false;
383                if (mPlayer != null) {
384                    try {
385                        mPlayer.stop();
386                    } catch (IllegalStateException e) {
387                        // Do nothing, the player is already stopped.
388                    }
389                }
390                Log.i("TTS", "Stopped");
391            }
392        } catch (InterruptedException e) {
393          Log.e("TTS stop", "tryLock interrupted");
394          e.printStackTrace();
395        } finally {
396            // This check is needed because finally will always run; even if the
397            // method returns somewhere in the try block.
398            if (speechQueueAvailable) {
399                speechQueueLock.unlock();
400            }
401            return result;
402        }
403    }
404
405    public void onCompletion(MediaPlayer arg0) {
406        processSpeechQueue();
407    }
408
409    private int playSilence(String callingApp, long duration, int queueMode,
410            ArrayList<String> params) {
411        if (queueMode == 0) {
412            stop(callingApp);
413        }
414        mSpeechQueue.add(new SpeechItem(callingApp, duration));
415        if (!mIsSpeaking) {
416            processSpeechQueue();
417        }
418        return TextToSpeech.TTS_SUCCESS;
419    }
420
421    private void silence(final long duration) {
422        class SilenceThread implements Runnable {
423            public void run() {
424                try {
425                    Thread.sleep(duration);
426                } catch (InterruptedException e) {
427                    e.printStackTrace();
428                } finally {
429                    processSpeechQueue();
430                }
431            }
432        }
433        Thread slnc = (new Thread(new SilenceThread()));
434        slnc.setPriority(Thread.MIN_PRIORITY);
435        slnc.start();
436    }
437
438    private void speakInternalOnly(final String text,
439            final ArrayList<String> params) {
440        class SynthThread implements Runnable {
441            public void run() {
442                boolean synthAvailable = false;
443                try {
444                    synthAvailable = synthesizerLock.tryLock();
445                    if (!synthAvailable) {
446                        Thread.sleep(100);
447                        Thread synth = (new Thread(new SynthThread()));
448                        synth.setPriority(Thread.MIN_PRIORITY);
449                        synth.start();
450                        return;
451                    }
452                    if (params != null){
453                        String language = "";
454                        String country = "";
455                        String variant = "";
456                        for (int i = 0; i < params.size() - 1; i = i + 2){
457                            String param = params.get(i);
458                            if (param.equals(TextToSpeech.Engine.TTS_KEY_PARAM_RATE)){
459                                setSpeechRate("", Integer.parseInt(params.get(i+1)));
460                            } else if (param.equals(TextToSpeech.Engine.TTS_KEY_PARAM_LANGUAGE)){
461                                language = params.get(i+1);
462                            } else if (param.equals(TextToSpeech.Engine.TTS_KEY_PARAM_COUNTRY)){
463                                country = params.get(i+1);
464                            } else if (param.equals(TextToSpeech.Engine.TTS_KEY_PARAM_VARIANT)){
465                                variant = params.get(i+1);
466                            }
467                        }
468                        if (language.length() > 0){
469                            setLanguage("", language, country, variant);
470                        }
471                    }
472                    nativeSynth.speak(text);
473                } catch (InterruptedException e) {
474                    Log.e("TTS speakInternalOnly", "tryLock interrupted");
475                    e.printStackTrace();
476                } finally {
477                    // This check is needed because finally will always run;
478                    // even if the
479                    // method returns somewhere in the try block.
480                    if (synthAvailable) {
481                        synthesizerLock.unlock();
482                    }
483                    processSpeechQueue();
484                }
485            }
486        }
487        Thread synth = (new Thread(new SynthThread()));
488        synth.setPriority(Thread.MIN_PRIORITY);
489        synth.start();
490    }
491
492    private void synthToFileInternalOnly(final String text,
493            final ArrayList<String> params, final String filename) {
494        class SynthThread implements Runnable {
495            public void run() {
496                Log.i("TTS", "Synthesizing to " + filename);
497                boolean synthAvailable = false;
498                try {
499                    synthAvailable = synthesizerLock.tryLock();
500                    if (!synthAvailable) {
501                        Thread.sleep(100);
502                        Thread synth = (new Thread(new SynthThread()));
503                        synth.setPriority(Thread.MIN_PRIORITY);
504                        synth.start();
505                        return;
506                    }
507                    if (params != null){
508                        String language = "";
509                        String country = "";
510                        String variant = "";
511                        for (int i = 0; i < params.size() - 1; i = i + 2){
512                            String param = params.get(i);
513                            if (param.equals(TextToSpeech.Engine.TTS_KEY_PARAM_RATE)){
514                                setSpeechRate("", Integer.parseInt(params.get(i+1)));
515                            } else if (param.equals(TextToSpeech.Engine.TTS_KEY_PARAM_LANGUAGE)){
516                                language = params.get(i+1);
517                            } else if (param.equals(TextToSpeech.Engine.TTS_KEY_PARAM_COUNTRY)){
518                                country = params.get(i+1);
519                            } else if (param.equals(TextToSpeech.Engine.TTS_KEY_PARAM_VARIANT)){
520                                variant = params.get(i+1);
521                            }
522                        }
523                        if (language.length() > 0){
524                            setLanguage("", language, country, variant);
525                        }
526                    }
527                    nativeSynth.synthesizeToFile(text, filename);
528                } catch (InterruptedException e) {
529                    Log.e("TTS synthToFileInternalOnly", "tryLock interrupted");
530                    e.printStackTrace();
531                } finally {
532                    // This check is needed because finally will always run;
533                    // even if the
534                    // method returns somewhere in the try block.
535                    if (synthAvailable) {
536                        synthesizerLock.unlock();
537                    }
538                    processSpeechQueue();
539                }
540            }
541        }
542        Thread synth = (new Thread(new SynthThread()));
543        synth.setPriority(Thread.MIN_PRIORITY);
544        synth.start();
545    }
546
547    private SoundResource getSoundResource(SpeechItem speechItem) {
548        SoundResource sr = null;
549        String text = speechItem.mText;
550        if (speechItem.mType == SpeechItem.SILENCE) {
551            // Do nothing if this is just silence
552        } else if (speechItem.mType == SpeechItem.EARCON) {
553            sr = mEarcons.get(text);
554        } else {
555            sr = mUtterances.get(text);
556        }
557        return sr;
558    }
559
560    private void broadcastTtsQueueProcessingCompleted(){
561        Intent i = new Intent(Intent.ACTION_TTS_QUEUE_PROCESSING_COMPLETED);
562        sendBroadcast(i);
563    }
564
565    private void dispatchSpeechCompletedCallbacks(String mark) {
566        Log.i("TTS callback", "dispatch started");
567        // Broadcast to all clients the new value.
568        final int N = mCallbacks.beginBroadcast();
569        for (int i = 0; i < N; i++) {
570            try {
571                mCallbacks.getBroadcastItem(i).markReached(mark);
572            } catch (RemoteException e) {
573                // The RemoteCallbackList will take care of removing
574                // the dead object for us.
575            }
576        }
577        mCallbacks.finishBroadcast();
578        Log.i("TTS callback", "dispatch completed to " + N);
579    }
580
581    private SpeechItem splitCurrentTextIfNeeded(SpeechItem currentSpeechItem){
582        if (currentSpeechItem.mText.length() < MAX_SPEECH_ITEM_CHAR_LENGTH){
583            return currentSpeechItem;
584        } else {
585            String callingApp = currentSpeechItem.callingApp;
586            ArrayList<SpeechItem> splitItems = new ArrayList<SpeechItem>();
587            int start = 0;
588            int end = start + MAX_SPEECH_ITEM_CHAR_LENGTH - 1;
589            String splitText;
590            SpeechItem splitItem;
591            while (end < currentSpeechItem.mText.length()){
592                splitText = currentSpeechItem.mText.substring(start, end);
593                splitItem = new SpeechItem(callingApp, splitText, null, SpeechItem.TEXT);
594                splitItems.add(splitItem);
595                start = end;
596                end = start + MAX_SPEECH_ITEM_CHAR_LENGTH - 1;
597            }
598            splitText = currentSpeechItem.mText.substring(start);
599            splitItem = new SpeechItem(callingApp, splitText, null, SpeechItem.TEXT);
600            splitItems.add(splitItem);
601            mSpeechQueue.remove(0);
602            for (int i = splitItems.size() - 1; i >= 0; i--){
603                mSpeechQueue.add(0, splitItems.get(i));
604            }
605            return mSpeechQueue.get(0);
606        }
607    }
608
609    private void processSpeechQueue() {
610        boolean speechQueueAvailable = false;
611        try {
612            speechQueueAvailable = speechQueueLock.tryLock();
613            if (!speechQueueAvailable) {
614                return;
615            }
616            if (mSpeechQueue.size() < 1) {
617                mIsSpeaking = false;
618                broadcastTtsQueueProcessingCompleted();
619                return;
620            }
621
622            SpeechItem currentSpeechItem = mSpeechQueue.get(0);
623            mIsSpeaking = true;
624            SoundResource sr = getSoundResource(currentSpeechItem);
625            // Synth speech as needed - synthesizer should call
626            // processSpeechQueue to continue running the queue
627            Log.i("TTS processing: ", currentSpeechItem.mText);
628            if (sr == null) {
629                if (currentSpeechItem.mType == SpeechItem.TEXT) {
630                    currentSpeechItem = splitCurrentTextIfNeeded(currentSpeechItem);
631                    speakInternalOnly(currentSpeechItem.mText,
632                            currentSpeechItem.mParams);
633                } else if (currentSpeechItem.mType == SpeechItem.TEXT_TO_FILE) {
634                    synthToFileInternalOnly(currentSpeechItem.mText,
635                            currentSpeechItem.mParams, currentSpeechItem.mFilename);
636                } else {
637                    // This is either silence or an earcon that was missing
638                    silence(currentSpeechItem.mDuration);
639                }
640            } else {
641                cleanUpPlayer();
642                if (sr.mSourcePackageName == PKGNAME) {
643                    // Utterance is part of the TTS library
644                    mPlayer = MediaPlayer.create(this, sr.mResId);
645                } else if (sr.mSourcePackageName != null) {
646                    // Utterance is part of the app calling the library
647                    Context ctx;
648                    try {
649                        ctx = this.createPackageContext(sr.mSourcePackageName,
650                                0);
651                    } catch (NameNotFoundException e) {
652                        e.printStackTrace();
653                        mSpeechQueue.remove(0); // Remove it from the queue and
654                        // move on
655                        mIsSpeaking = false;
656                        return;
657                    }
658                    mPlayer = MediaPlayer.create(ctx, sr.mResId);
659                } else {
660                    // Utterance is coming from a file
661                    mPlayer = MediaPlayer.create(this, Uri.parse(sr.mFilename));
662                }
663
664                // Check if Media Server is dead; if it is, clear the queue and
665                // give up for now - hopefully, it will recover itself.
666                if (mPlayer == null) {
667                    mSpeechQueue.clear();
668                    mIsSpeaking = false;
669                    return;
670                }
671                mPlayer.setOnCompletionListener(this);
672                try {
673                    mPlayer.start();
674                } catch (IllegalStateException e) {
675                    mSpeechQueue.clear();
676                    mIsSpeaking = false;
677                    cleanUpPlayer();
678                    return;
679                }
680            }
681            if (mSpeechQueue.size() > 0) {
682                mSpeechQueue.remove(0);
683            }
684        } finally {
685            // This check is needed because finally will always run; even if the
686            // method returns somewhere in the try block.
687            if (speechQueueAvailable) {
688                speechQueueLock.unlock();
689            }
690        }
691    }
692
693    private void cleanUpPlayer() {
694        if (mPlayer != null) {
695            mPlayer.release();
696            mPlayer = null;
697        }
698    }
699
700    /**
701     * Synthesizes the given text to a file using the specified parameters.
702     *
703     * @param text
704     *            The String of text that should be synthesized
705     * @param params
706     *            An ArrayList of parameters. The first element of this array
707     *            controls the type of voice to use.
708     * @param filename
709     *            The string that gives the full output filename; it should be
710     *            something like "/sdcard/myappsounds/mysound.wav".
711     * @return A boolean that indicates if the synthesis succeeded
712     */
713    private boolean synthesizeToFile(String callingApp, String text, ArrayList<String> params,
714            String filename) {
715        // Don't allow a filename that is too long
716        if (filename.length() > MAX_FILENAME_LENGTH) {
717            return false;
718        }
719        // Don't allow anything longer than the max text length; since this
720        // is synthing to a file, don't even bother splitting it.
721        if (text.length() >= MAX_SPEECH_ITEM_CHAR_LENGTH){
722            return false;
723        }
724        mSpeechQueue.add(new SpeechItem(callingApp, text, params, SpeechItem.TEXT_TO_FILE, filename));
725        if (!mIsSpeaking) {
726            processSpeechQueue();
727        }
728        return true;
729    }
730
731    @Override
732    public IBinder onBind(Intent intent) {
733        if (ACTION.equals(intent.getAction())) {
734            for (String category : intent.getCategories()) {
735                if (category.equals(CATEGORY)) {
736                    return mBinder;
737                }
738            }
739        }
740        return null;
741    }
742
743    private final android.speech.tts.ITts.Stub mBinder = new Stub() {
744
745        public void registerCallback(ITtsCallback cb) {
746            if (cb != null)
747                mCallbacks.register(cb);
748        }
749
750        public void unregisterCallback(ITtsCallback cb) {
751            if (cb != null)
752                mCallbacks.unregister(cb);
753        }
754
755        /**
756         * Speaks the given text using the specified queueing mode and
757         * parameters.
758         *
759         * @param text
760         *            The text that should be spoken
761         * @param queueMode
762         *            0 for no queue (interrupts all previous utterances), 1 for
763         *            queued
764         * @param params
765         *            An ArrayList of parameters. The first element of this
766         *            array controls the type of voice to use.
767         */
768        public int speak(String callingApp, String text, int queueMode, String[] params) {
769            ArrayList<String> speakingParams = new ArrayList<String>();
770            if (params != null) {
771                speakingParams = new ArrayList<String>(Arrays.asList(params));
772            }
773            return mSelf.speak(callingApp, text, queueMode, speakingParams);
774        }
775
776        /**
777         * Plays the earcon using the specified queueing mode and parameters.
778         *
779         * @param earcon
780         *            The earcon that should be played
781         * @param queueMode
782         *            0 for no queue (interrupts all previous utterances), 1 for
783         *            queued
784         * @param params
785         *            An ArrayList of parameters.
786         */
787        public int playEarcon(String callingApp, String earcon, int queueMode, String[] params) {
788            ArrayList<String> speakingParams = new ArrayList<String>();
789            if (params != null) {
790                speakingParams = new ArrayList<String>(Arrays.asList(params));
791            }
792            return mSelf.playEarcon(callingApp, earcon, queueMode, speakingParams);
793        }
794
795        /**
796         * Plays the silence using the specified queueing mode and parameters.
797         *
798         * @param duration
799         *            The duration of the silence that should be played
800         * @param queueMode
801         *            0 for no queue (interrupts all previous utterances), 1 for
802         *            queued
803         * @param params
804         *            An ArrayList of parameters.
805         */
806        public int playSilence(String callingApp, long duration, int queueMode, String[] params) {
807            ArrayList<String> speakingParams = new ArrayList<String>();
808            if (params != null) {
809                speakingParams = new ArrayList<String>(Arrays.asList(params));
810            }
811            return mSelf.playSilence(callingApp, duration, queueMode, speakingParams);
812        }
813
814        /**
815         * Stops all speech output and removes any utterances still in the
816         * queue.
817         */
818        public int stop(String callingApp) {
819            return mSelf.stop(callingApp);
820        }
821
822        /**
823         * Returns whether or not the TTS is speaking.
824         *
825         * @return Boolean to indicate whether or not the TTS is speaking
826         */
827        public boolean isSpeaking() {
828            return (mSelf.mIsSpeaking && (mSpeechQueue.size() < 1));
829        }
830
831        /**
832         * Adds a sound resource to the TTS.
833         *
834         * @param text
835         *            The text that should be associated with the sound resource
836         * @param packageName
837         *            The name of the package which has the sound resource
838         * @param resId
839         *            The resource ID of the sound within its package
840         */
841        public void addSpeech(String callingApp, String text, String packageName, int resId) {
842            mSelf.addSpeech(callingApp, text, packageName, resId);
843        }
844
845        /**
846         * Adds a sound resource to the TTS.
847         *
848         * @param text
849         *            The text that should be associated with the sound resource
850         * @param filename
851         *            The filename of the sound resource. This must be a
852         *            complete path like: (/sdcard/mysounds/mysoundbite.mp3).
853         */
854        public void addSpeechFile(String callingApp, String text, String filename) {
855            mSelf.addSpeech(callingApp, text, filename);
856        }
857
858        /**
859         * Adds a sound resource to the TTS as an earcon.
860         *
861         * @param earcon
862         *            The text that should be associated with the sound resource
863         * @param packageName
864         *            The name of the package which has the sound resource
865         * @param resId
866         *            The resource ID of the sound within its package
867         */
868        public void addEarcon(String callingApp, String earcon, String packageName, int resId) {
869            mSelf.addEarcon(callingApp, earcon, packageName, resId);
870        }
871
872        /**
873         * Adds a sound resource to the TTS as an earcon.
874         *
875         * @param earcon
876         *            The text that should be associated with the sound resource
877         * @param filename
878         *            The filename of the sound resource. This must be a
879         *            complete path like: (/sdcard/mysounds/mysoundbite.mp3).
880         */
881        public void addEarconFile(String callingApp, String earcon, String filename) {
882            mSelf.addEarcon(callingApp, earcon, filename);
883        }
884
885        /**
886         * Sets the speech rate for the TTS. Note that this will only have an
887         * effect on synthesized speech; it will not affect pre-recorded speech.
888         *
889         * @param speechRate
890         *            The speech rate that should be used
891         */
892        public int setSpeechRate(String callingApp, int speechRate) {
893            return mSelf.setSpeechRate(callingApp, speechRate);
894        }
895
896        /**
897         * Sets the pitch for the TTS. Note that this will only have an
898         * effect on synthesized speech; it will not affect pre-recorded speech.
899         *
900         * @param pitch
901         *            The pitch that should be used for the synthesized voice
902         */
903        public int setPitch(String callingApp, int pitch) {
904            return mSelf.setPitch(callingApp, pitch);
905        }
906
907        /**
908         * Returns the level of support for the specified language.
909         *
910         * @param lang  the three letter ISO language code.
911         * @param country  the three letter ISO country code.
912         * @param variant  the variant code associated with the country and language pair.
913         * @return one of TTS_LANG_NOT_SUPPORTED, TTS_LANG_MISSING_DATA, TTS_LANG_AVAILABLE,
914         *      TTS_LANG_COUNTRY_AVAILABLE, TTS_LANG_COUNTRY_VAR_AVAILABLE as defined in
915         *      android.speech.tts.TextToSpeech.
916         */
917        public int isLanguageAvailable(String lang, String country, String variant) {
918            return mSelf.isLanguageAvailable(lang, country, variant);
919        }
920
921        /**
922         * Returns the currently set language / country / variant strings representing the
923         * language used by the TTS engine.
924         * @return null is no language is set, or an array of 3 string containing respectively
925         *      the language, country and variant.
926         */
927        public String[] getLanguage() {
928            return mSelf.getLanguage();
929        }
930
931        /**
932         * Sets the speech rate for the TTS, which affects the synthesized voice.
933         *
934         * @param lang  the three letter ISO language code.
935         * @param country  the three letter ISO country code.
936         * @param variant  the variant code associated with the country and language pair.
937         */
938        public int setLanguage(String callingApp, String lang, String country, String variant) {
939            return mSelf.setLanguage(callingApp, lang, country, variant);
940        }
941
942        /**
943         * Synthesizes the given text to a file using the specified
944         * parameters.
945         *
946         * @param text
947         *            The String of text that should be synthesized
948         * @param params
949         *            An ArrayList of parameters. The first element of this
950         *            array controls the type of voice to use.
951         * @param filename
952         *            The string that gives the full output filename; it should
953         *            be something like "/sdcard/myappsounds/mysound.wav".
954         * @return A boolean that indicates if the synthesis succeeded
955         */
956        public boolean synthesizeToFile(String callingApp, String text, String[] params,
957                String filename) {
958            ArrayList<String> speakingParams = new ArrayList<String>();
959            if (params != null) {
960                speakingParams = new ArrayList<String>(Arrays.asList(params));
961            }
962            return mSelf.synthesizeToFile(callingApp, text, speakingParams, filename);
963        }
964
965    };
966
967}
968