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