TextToSpeech.java revision 77a5d39343760d9950ca15a87db0ae778afb4f2b
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.speech.tts;
17
18import android.speech.tts.ITts;
19import android.speech.tts.ITtsCallback;
20
21import android.annotation.SdkConstant;
22import android.annotation.SdkConstant.SdkConstantType;
23import android.content.ComponentName;
24import android.content.Context;
25import android.content.Intent;
26import android.content.ServiceConnection;
27import android.media.AudioManager;
28import android.os.IBinder;
29import android.os.RemoteException;
30import android.util.Log;
31
32import java.util.HashMap;
33import java.util.Locale;
34
35/**
36 *
37 * Synthesizes speech from text for immediate playback or to create a sound file.
38 *
39 */
40//TODO complete javadoc + add links to constants
41public class TextToSpeech {
42
43    /**
44     * Denotes a successful operation.
45     */
46    public static final int SUCCESS                = 0;
47    /**
48     * Denotes a generic operation failure.
49     */
50    public static final int ERROR                  = -1;
51
52    /**
53     * Queue mode where all entries in the playback queue (media to be played
54     * and text to be synthesized) are dropped and replaced by the new entry.
55     */
56    public static final int QUEUE_FLUSH = 0;
57    /**
58     * Queue mode where the new entry is added at the end of the playback queue.
59     */
60    public static final int QUEUE_ADD = 1;
61
62
63    /**
64     * Denotes the language is available exactly as specified by the locale
65     */
66    public static final int LANG_COUNTRY_VAR_AVAILABLE = 2;
67
68
69    /**
70     * Denotes the language is available for the language and country specified
71     * by the locale, but not the variant.
72     */
73    public static final int LANG_COUNTRY_AVAILABLE = 1;
74
75
76    /**
77     * Denotes the language is available for the language by the locale,
78     * but not the country and variant.
79     */
80    public static final int LANG_AVAILABLE = 0;
81
82    /**
83     * Denotes the language data is missing.
84     */
85    public static final int LANG_MISSING_DATA = -1;
86
87    /**
88     * Denotes the language is not supported by the current TTS engine.
89     */
90    public static final int LANG_NOT_SUPPORTED = -2;
91
92
93    /**
94     * Broadcast Action: The TextToSpeech synthesizer has completed processing
95     * of all the text in the speech queue.
96     */
97    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
98    public static final String ACTION_TTS_QUEUE_PROCESSING_COMPLETED =
99            "android.speech.tts.TTS_QUEUE_PROCESSING_COMPLETED";
100
101
102    /**
103     * Called when the TTS has initialized.
104     *
105     * The InitListener must implement the onInit function. onInit is passed a
106     * status code indicating the result of the TTS initialization.
107     */
108    public interface OnInitListener {
109        public void onInit(int status);
110    }
111
112    /**
113     * Called when the TTS has completed saying something that has an utterance ID set.
114     *
115     * The OnUtteranceCompletedListener must implement the onUtteranceCompleted function.
116     * onUtteranceCompleted is passed a String that is the utteranceId given in
117     * the original speak call.
118     */
119    public interface OnUtteranceCompletedListener {
120        public void onUtteranceCompleted(String utteranceId);
121    }
122
123
124    /**
125     * Internal constants for the TTS functionality
126     *
127     */
128    public class Engine {
129        // default values for a TTS engine when settings are not found in the provider
130        /**
131         * {@hide}
132         */
133        public static final int DEFAULT_RATE = 100; // 1x
134        /**
135         * {@hide}
136         */
137        public static final int DEFAULT_PITCH = 100;// 1x
138        /**
139         * {@hide}
140         */
141        public static final int USE_DEFAULTS = 0; // false
142        /**
143         * {@hide}
144         */
145        public static final String DEFAULT_SYNTH = "com.svox.pico";
146
147        // default values for rendering
148        public static final int DEFAULT_STREAM = AudioManager.STREAM_MUSIC;
149
150        // return codes for a TTS engine's check data activity
151        /**
152         * Indicates success when checking the installation status of the resources used by the
153         * text-to-speech engine with the {@link #ACTION_CHECK_TTS_DATA} intent.
154         */
155        public static final int CHECK_VOICE_DATA_PASS = 1;
156        /**
157         * Indicates failure when checking the installation status of the resources used by the
158         * text-to-speech engine with the {@link #ACTION_CHECK_TTS_DATA} intent.
159         */
160        public static final int CHECK_VOICE_DATA_FAIL = 0;
161        /**
162         * Indicates erroneous data when checking the installation status of the resources used by
163         * the text-to-speech engine with the {@link #ACTION_CHECK_TTS_DATA} intent.
164         */
165        public static final int CHECK_VOICE_DATA_BAD_DATA = -1;
166        /**
167         * Indicates missing resources when checking the installation status of the resources used
168         * by the text-to-speech engine with the {@link #ACTION_CHECK_TTS_DATA} intent.
169         */
170        public static final int CHECK_VOICE_DATA_MISSING_DATA = -2;
171        /**
172         * Indicates missing storage volume when checking the installation status of the resources
173         * used by the text-to-speech engine with the {@link #ACTION_CHECK_TTS_DATA} intent.
174         */
175        public static final int CHECK_VOICE_DATA_MISSING_VOLUME = -3;
176
177        // intents to ask engine to install data or check its data
178        /**
179         * Broadcast Action: Triggers the platform Text-To-Speech engine to
180         * start the activity that installs the resource files on the device
181         * that are required for TTS to be operational. Since the installation
182         * of the data can be interrupted or declined by the user, the application
183         * shouldn't expect successful installation upon return from that intent,
184         * and if need be, should check installation status with
185         * {@link #ACTION_CHECK_TTS_DATA}.
186         */
187        @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
188        public static final String ACTION_INSTALL_TTS_DATA =
189                "android.speech.tts.engine.INSTALL_TTS_DATA";
190
191        /**
192         * {@hide}
193         */
194        @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
195        public static final String ACTION_TTS_DATA_INSTALLED =
196                "android.speech.tts.engine.TTS_DATA_INSTALLED";
197        /**
198         * Broadcast Action: Starts the activity from the platform Text-To-Speech
199         * engine to verify the proper installation and availability of the
200         * resource files on the system. Upon completion, the activity will
201         * return one of the following codes:
202         * {@link #CHECK_VOICE_DATA_PASS},
203         * {@link #CHECK_VOICE_DATA_FAIL},
204         * {@link #CHECK_VOICE_DATA_BAD_DATA},
205         * {@link #CHECK_VOICE_DATA_MISSING_DATA}, or
206         * {@link #CHECK_VOICE_DATA_MISSING_VOLUME}.
207         * <p> Moreover, the data received in the activity result will contain the following
208         * fields:
209         * <ul>
210         *   <li>{@link #EXTRA_VOICE_DATA_ROOT_DIRECTORY} which
211         *       indicates the path to the location of the resource files</li>,
212         *   <li>{@link #EXTRA_VOICE_DATA_FILES} which contains
213         *       the list of all the resource files</li>,
214         *   <li>and {@link #EXTRA_VOICE_DATA_FILES_INFO} which
215         *       contains, for each resource file, the description of the language covered by
216         *       the file in the xxx-YYY format, where xxx is the 3-letter ISO language code,
217         *       and YYY is the 3-letter ISO country code.</li>
218         * </ul>
219         */
220        @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
221        public static final String ACTION_CHECK_TTS_DATA =
222                "android.speech.tts.engine.CHECK_TTS_DATA";
223
224        // extras for a TTS engine's check data activity
225        /**
226         * Extra information received with the {@link #ACTION_CHECK_TTS_DATA} intent where
227         * the text-to-speech engine specifies the path to its resources.
228         */
229        public static final String EXTRA_VOICE_DATA_ROOT_DIRECTORY = "dataRoot";
230        /**
231         * Extra information received with the {@link #ACTION_CHECK_TTS_DATA} intent where
232         * the text-to-speech engine specifies the file names of its resources under the
233         * resource path.
234         */
235        public static final String EXTRA_VOICE_DATA_FILES = "dataFiles";
236        /**
237         * Extra information received with the {@link #ACTION_CHECK_TTS_DATA} intent where
238         * the text-to-speech engine specifies the locale associated with each resource file.
239         */
240        public static final String EXTRA_VOICE_DATA_FILES_INFO = "dataFilesInfo";
241
242        // extras for a TTS engine's data installation
243        /**
244         * Extra information received with the {@link #ACTION_TTS_DATA_INSTALLED} intent
245         * which indicates whether the TTS data installation requested with
246         * {@link #ACTION_INSTALL_TTS_DATA} completed successfully or not. The value is
247         * {@link TextToSpeech#SUCCESS} or {@link TextToSpeech#ERROR}.
248         * {@hide}
249         */
250        public static final String EXTRA_TTS_DATA_INSTALLED = "dataInstalled";
251
252        // keys for the parameters passed with speak commands. Hidden keys are used internally
253        // to maintain engine state for each TextToSpeech instance.
254        /**
255         * {@hide}
256         */
257        public static final String KEY_PARAM_RATE = "rate";
258        /**
259         * {@hide}
260         */
261        public static final String KEY_PARAM_LANGUAGE = "language";
262        /**
263         * {@hide}
264         */
265        public static final String KEY_PARAM_COUNTRY = "country";
266        /**
267         * {@hide}
268         */
269        public static final String KEY_PARAM_VARIANT = "variant";
270        /**
271         * Parameter key to specify the audio stream type to be used when speaking text
272         * or playing back a file.
273         */
274        public static final String KEY_PARAM_STREAM = "streamType";
275        /**
276         * Parameter key to identify an utterance in the completion listener after text has been
277         * spoken, a file has been played back or a silence duration has elapsed.
278         */
279        public static final String KEY_PARAM_UTTERANCE_ID = "utteranceId";
280
281        // key positions in the array of cached parameters
282        /**
283         * {@hide}
284         */
285        protected static final int PARAM_POSITION_RATE = 0;
286        /**
287         * {@hide}
288         */
289        protected static final int PARAM_POSITION_LANGUAGE = 2;
290        /**
291         * {@hide}
292         */
293        protected static final int PARAM_POSITION_COUNTRY = 4;
294        /**
295         * {@hide}
296         */
297        protected static final int PARAM_POSITION_VARIANT = 6;
298        /**
299         * {@hide}
300         */
301        protected static final int PARAM_POSITION_STREAM = 8;
302        /**
303         * {@hide}
304         */
305        protected static final int PARAM_POSITION_UTTERANCE_ID = 10;
306        /**
307         * {@hide}
308         */
309        protected static final int NB_CACHED_PARAMS = 6;
310    }
311
312    /**
313     * Connection needed for the TTS.
314     */
315    private ServiceConnection mServiceConnection;
316
317    private ITts mITts = null;
318    private ITtsCallback mITtscallback = null;
319    private Context mContext = null;
320    private String mPackageName = "";
321    private OnInitListener mInitListener = null;
322    private boolean mStarted = false;
323    private final Object mStartLock = new Object();
324    /**
325     * Used to store the cached parameters sent along with each synthesis request to the
326     * TTS service.
327     */
328    private String[] mCachedParams;
329
330    /**
331     * The constructor for the TTS.
332     *
333     * @param context
334     *            The context
335     * @param listener
336     *            The InitListener that will be called when the TTS has
337     *            initialized successfully.
338     */
339    public TextToSpeech(Context context, OnInitListener listener) {
340        mContext = context;
341        mPackageName = mContext.getPackageName();
342        mInitListener = listener;
343
344        mCachedParams = new String[2*Engine.NB_CACHED_PARAMS]; // store key and value
345        mCachedParams[Engine.PARAM_POSITION_RATE] = Engine.KEY_PARAM_RATE;
346        mCachedParams[Engine.PARAM_POSITION_LANGUAGE] = Engine.KEY_PARAM_LANGUAGE;
347        mCachedParams[Engine.PARAM_POSITION_COUNTRY] = Engine.KEY_PARAM_COUNTRY;
348        mCachedParams[Engine.PARAM_POSITION_VARIANT] = Engine.KEY_PARAM_VARIANT;
349        mCachedParams[Engine.PARAM_POSITION_STREAM] = Engine.KEY_PARAM_STREAM;
350        mCachedParams[Engine.PARAM_POSITION_UTTERANCE_ID] = Engine.KEY_PARAM_UTTERANCE_ID;
351
352        mCachedParams[Engine.PARAM_POSITION_RATE + 1] =
353                String.valueOf(Engine.DEFAULT_RATE);
354        // initialize the language cached parameters with the current Locale
355        Locale defaultLoc = Locale.getDefault();
356        mCachedParams[Engine.PARAM_POSITION_LANGUAGE + 1] = defaultLoc.getISO3Language();
357        mCachedParams[Engine.PARAM_POSITION_COUNTRY + 1] = defaultLoc.getISO3Country();
358        mCachedParams[Engine.PARAM_POSITION_VARIANT + 1] = defaultLoc.getVariant();
359
360        mCachedParams[Engine.PARAM_POSITION_STREAM + 1] =
361                String.valueOf(Engine.DEFAULT_STREAM);
362        mCachedParams[Engine.PARAM_POSITION_UTTERANCE_ID + 1] = "";
363
364        initTts();
365    }
366
367
368    private void initTts() {
369        mStarted = false;
370
371        // Initialize the TTS, run the callback after the binding is successful
372        mServiceConnection = new ServiceConnection() {
373            public void onServiceConnected(ComponentName name, IBinder service) {
374                synchronized(mStartLock) {
375                    mITts = ITts.Stub.asInterface(service);
376                    mStarted = true;
377                    if (mInitListener != null) {
378                        // TODO manage failures and missing resources
379                        mInitListener.onInit(SUCCESS);
380                    }
381                }
382            }
383
384            public void onServiceDisconnected(ComponentName name) {
385                synchronized(mStartLock) {
386                    mITts = null;
387                    mInitListener = null;
388                    mStarted = false;
389                }
390            }
391        };
392
393        Intent intent = new Intent("android.intent.action.START_TTS_SERVICE");
394        intent.addCategory("android.intent.category.TTS");
395        mContext.bindService(intent, mServiceConnection,
396                Context.BIND_AUTO_CREATE);
397        // TODO handle case where the binding works (should always work) but
398        //      the plugin fails
399    }
400
401
402    /**
403     * Shuts down the TTS. It is good practice to call this in the onDestroy
404     * method of the Activity that is using the TTS so that the TTS is stopped
405     * cleanly.
406     */
407    public void shutdown() {
408        try {
409            mContext.unbindService(mServiceConnection);
410        } catch (IllegalArgumentException e) {
411            // Do nothing and fail silently since an error here indicates that
412            // binding never succeeded in the first place.
413        }
414    }
415
416
417    /**
418     * Adds a mapping between a string of text and a sound resource in a
419     * package.
420     * @see #speak(String, int, HashMap)
421     *
422     * @param text
423     *            Example: <b><code>"south_south_east"</code></b><br/>
424     *
425     * @param packagename
426     *            Pass the packagename of the application that contains the
427     *            resource. If the resource is in your own application (this is
428     *            the most common case), then put the packagename of your
429     *            application here.<br/>
430     *            Example: <b>"com.google.marvin.compass"</b><br/>
431     *            The packagename can be found in the AndroidManifest.xml of
432     *            your application.
433     *            <p>
434     *            <code>&lt;manifest xmlns:android=&quot;...&quot;
435     *      package=&quot;<b>com.google.marvin.compass</b>&quot;&gt;</code>
436     *            </p>
437     *
438     * @param resourceId
439     *            Example: <b><code>R.raw.south_south_east</code></b>
440     *
441     * @return Code indicating success or failure. See {@link #ERROR} and {@link #SUCCESS}.
442     */
443    public int addSpeech(String text, String packagename, int resourceId) {
444        synchronized(mStartLock) {
445            if (!mStarted) {
446                return ERROR;
447            }
448            try {
449                mITts.addSpeech(mPackageName, text, packagename, resourceId);
450                return SUCCESS;
451            } catch (RemoteException e) {
452                // TTS died; restart it.
453                Log.e("TextToSpeech.java - addSpeech", "RemoteException");
454                e.printStackTrace();
455                mStarted = false;
456                initTts();
457            } catch (NullPointerException e) {
458                // TTS died; restart it.
459                Log.e("TextToSpeech.java - addSpeech", "NullPointerException");
460                e.printStackTrace();
461                mStarted = false;
462                initTts();
463            } catch (IllegalStateException e) {
464                // TTS died; restart it.
465                Log.e("TextToSpeech.java - addSpeech", "IllegalStateException");
466                e.printStackTrace();
467                mStarted = false;
468                initTts();
469            }
470            return ERROR;
471        }
472    }
473
474
475    /**
476     * Adds a mapping between a string of text and a sound file. Using this, it
477     * is possible to add custom pronounciations for text.
478     *
479     * @param text
480     *            The string of text
481     * @param filename
482     *            The full path to the sound file (for example:
483     *            "/sdcard/mysounds/hello.wav")
484     *
485     * @return Code indicating success or failure. See {@link #ERROR} and {@link #SUCCESS}.
486     */
487    public int addSpeech(String text, String filename) {
488        synchronized (mStartLock) {
489            if (!mStarted) {
490                return ERROR;
491            }
492            try {
493                mITts.addSpeechFile(mPackageName, text, filename);
494                return SUCCESS;
495            } catch (RemoteException e) {
496                // TTS died; restart it.
497                Log.e("TextToSpeech.java - addSpeech", "RemoteException");
498                e.printStackTrace();
499                mStarted = false;
500                initTts();
501            } catch (NullPointerException e) {
502                // TTS died; restart it.
503                Log.e("TextToSpeech.java - addSpeech", "NullPointerException");
504                e.printStackTrace();
505                mStarted = false;
506                initTts();
507            } catch (IllegalStateException e) {
508                // TTS died; restart it.
509                Log.e("TextToSpeech.java - addSpeech", "IllegalStateException");
510                e.printStackTrace();
511                mStarted = false;
512                initTts();
513            }
514            return ERROR;
515        }
516    }
517
518
519    /**
520     * Adds a mapping between a string of text and a sound resource in a
521     * package.
522     *
523     * @see #playEarcon(String, int, HashMap)
524     *
525     * @param earcon The name of the earcon
526     *            Example: <b><code>"[tick]"</code></b><br/>
527     *
528     * @param packagename
529     *            Pass the packagename of the application that contains the
530     *            resource. If the resource is in your own application (this is
531     *            the most common case), then put the packagename of your
532     *            application here.<br/>
533     *            Example: <b>"com.google.marvin.compass"</b><br/>
534     *            The packagename can be found in the AndroidManifest.xml of
535     *            your application.
536     *            <p>
537     *            <code>&lt;manifest xmlns:android=&quot;...&quot;
538     *      package=&quot;<b>com.google.marvin.compass</b>&quot;&gt;</code>
539     *            </p>
540     *
541     * @param resourceId
542     *            Example: <b><code>R.raw.tick_snd</code></b>
543     *
544     * @return Code indicating success or failure. See {@link #ERROR} and {@link #SUCCESS}.
545     */
546    public int addEarcon(String earcon, String packagename, int resourceId) {
547        synchronized(mStartLock) {
548            if (!mStarted) {
549                return ERROR;
550            }
551            try {
552                mITts.addEarcon(mPackageName, earcon, packagename, resourceId);
553                return SUCCESS;
554            } catch (RemoteException e) {
555                // TTS died; restart it.
556                Log.e("TextToSpeech.java - addEarcon", "RemoteException");
557                e.printStackTrace();
558                mStarted = false;
559                initTts();
560            } catch (NullPointerException e) {
561                // TTS died; restart it.
562                Log.e("TextToSpeech.java - addEarcon", "NullPointerException");
563                e.printStackTrace();
564                mStarted = false;
565                initTts();
566            } catch (IllegalStateException e) {
567                // TTS died; restart it.
568                Log.e("TextToSpeech.java - addEarcon", "IllegalStateException");
569                e.printStackTrace();
570                mStarted = false;
571                initTts();
572            }
573            return ERROR;
574        }
575    }
576
577
578    /**
579     * Adds a mapping between a string of text and a sound file. Using this, it
580     * is possible to add custom earcons.
581     *
582     * @param earcon
583     *            The name of the earcon
584     * @param filename
585     *            The full path to the sound file (for example:
586     *            "/sdcard/mysounds/tick.wav")
587     *
588     * @return Code indicating success or failure. See {@link #ERROR} and {@link #SUCCESS}.
589     */
590    public int addEarcon(String earcon, String filename) {
591        synchronized (mStartLock) {
592            if (!mStarted) {
593                return ERROR;
594            }
595            try {
596                mITts.addEarconFile(mPackageName, earcon, filename);
597                return SUCCESS;
598            } catch (RemoteException e) {
599                // TTS died; restart it.
600                Log.e("TextToSpeech.java - addEarcon", "RemoteException");
601                e.printStackTrace();
602                mStarted = false;
603                initTts();
604            } catch (NullPointerException e) {
605                // TTS died; restart it.
606                Log.e("TextToSpeech.java - addEarcon", "NullPointerException");
607                e.printStackTrace();
608                mStarted = false;
609                initTts();
610            } catch (IllegalStateException e) {
611                // TTS died; restart it.
612                Log.e("TextToSpeech.java - addEarcon", "IllegalStateException");
613                e.printStackTrace();
614                mStarted = false;
615                initTts();
616            }
617            return ERROR;
618        }
619    }
620
621
622    /**
623     * Speaks the string using the specified queuing strategy and speech
624     * parameters. Note that the speech parameters are not universally supported
625     * by all engines and will be treated as a hint. The TTS library will try to
626     * fulfill these parameters as much as possible, but there is no guarantee
627     * that the voice used will have the properties specified.
628     *
629     * @param text
630     *            The string of text to be spoken.
631     * @param queueMode
632     *            The queuing strategy to use.
633     *            See QUEUE_ADD and QUEUE_FLUSH.
634     * @param params
635     *            The hashmap of speech parameters to be used.
636     *
637     * @return Code indicating success or failure. See {@link #ERROR} and {@link #SUCCESS}.
638     */
639    public int speak(String text, int queueMode, HashMap<String,String> params)
640    {
641        synchronized (mStartLock) {
642            int result = ERROR;
643            Log.i("TTS received: ", text);
644            if (!mStarted) {
645                return result;
646            }
647            try {
648                if ((params != null) && (!params.isEmpty())) {
649                    String extra = params.get(Engine.KEY_PARAM_STREAM);
650                    if (extra != null) {
651                        mCachedParams[Engine.PARAM_POSITION_STREAM + 1] = extra;
652                    }
653                    extra = params.get(Engine.KEY_PARAM_UTTERANCE_ID);
654                    if (extra != null) {
655                        mCachedParams[Engine.PARAM_POSITION_UTTERANCE_ID + 1] = extra;
656                    }
657                }
658                result = mITts.speak(mPackageName, text, queueMode, mCachedParams);
659            } catch (RemoteException e) {
660                // TTS died; restart it.
661                Log.e("TextToSpeech.java - speak", "RemoteException");
662                e.printStackTrace();
663                mStarted = false;
664                initTts();
665            } catch (NullPointerException e) {
666                // TTS died; restart it.
667                Log.e("TextToSpeech.java - speak", "NullPointerException");
668                e.printStackTrace();
669                mStarted = false;
670                initTts();
671            } catch (IllegalStateException e) {
672                // TTS died; restart it.
673                Log.e("TextToSpeech.java - speak", "IllegalStateException");
674                e.printStackTrace();
675                mStarted = false;
676                initTts();
677            } finally {
678                resetCachedParams();
679                return result;
680            }
681        }
682    }
683
684
685    /**
686     * Plays the earcon using the specified queueing mode and parameters.
687     *
688     * @param earcon
689     *            The earcon that should be played
690     * @param queueMode
691     *            See QUEUE_ADD and QUEUE_FLUSH.
692     * @param params
693     *            The hashmap of parameters to be used.
694     *
695     * @return Code indicating success or failure. See {@link #ERROR} and {@link #SUCCESS}.
696     */
697    public int playEarcon(String earcon, int queueMode,
698            HashMap<String,String> params) {
699        synchronized (mStartLock) {
700            int result = ERROR;
701            if (!mStarted) {
702                return result;
703            }
704            try {
705                if ((params != null) && (!params.isEmpty())) {
706                    String extra = params.get(Engine.KEY_PARAM_STREAM);
707                    if (extra != null) {
708                        mCachedParams[Engine.PARAM_POSITION_STREAM + 1] = extra;
709                    }
710                    extra = params.get(Engine.KEY_PARAM_UTTERANCE_ID);
711                    if (extra != null) {
712                        mCachedParams[Engine.PARAM_POSITION_UTTERANCE_ID + 1] = extra;
713                    }
714                }
715                result = mITts.playEarcon(mPackageName, earcon, queueMode, null);
716            } catch (RemoteException e) {
717                // TTS died; restart it.
718                Log.e("TextToSpeech.java - playEarcon", "RemoteException");
719                e.printStackTrace();
720                mStarted = false;
721                initTts();
722            } catch (NullPointerException e) {
723                // TTS died; restart it.
724                Log.e("TextToSpeech.java - playEarcon", "NullPointerException");
725                e.printStackTrace();
726                mStarted = false;
727                initTts();
728            } catch (IllegalStateException e) {
729                // TTS died; restart it.
730                Log.e("TextToSpeech.java - playEarcon", "IllegalStateException");
731                e.printStackTrace();
732                mStarted = false;
733                initTts();
734            } finally {
735                resetCachedParams();
736                return result;
737            }
738        }
739    }
740
741    /**
742     * Plays silence for the specified amount of time using the specified
743     * queue mode.
744     *
745     * @param durationInMs
746     *            A long that indicates how long the silence should last.
747     * @param queueMode
748     *            See QUEUE_ADD and QUEUE_FLUSH.
749     *
750     * @return Code indicating success or failure. See {@link #ERROR} and {@link #SUCCESS}.
751     */
752    public int playSilence(long durationInMs, int queueMode, HashMap<String,String> params) {
753        synchronized (mStartLock) {
754            int result = ERROR;
755            if (!mStarted) {
756                return result;
757            }
758            try {
759                if ((params != null) && (!params.isEmpty())) {
760                    String extra = params.get(Engine.KEY_PARAM_UTTERANCE_ID);
761                    if (extra != null) {
762                        mCachedParams[Engine.PARAM_POSITION_UTTERANCE_ID + 1] = extra;
763                    }
764                }
765                result = mITts.playSilence(mPackageName, durationInMs, queueMode, mCachedParams);
766            } catch (RemoteException e) {
767                // TTS died; restart it.
768                Log.e("TextToSpeech.java - playSilence", "RemoteException");
769                e.printStackTrace();
770                mStarted = false;
771                initTts();
772            } catch (NullPointerException e) {
773                // TTS died; restart it.
774                Log.e("TextToSpeech.java - playSilence", "NullPointerException");
775                e.printStackTrace();
776                mStarted = false;
777                initTts();
778            } catch (IllegalStateException e) {
779                // TTS died; restart it.
780                Log.e("TextToSpeech.java - playSilence", "IllegalStateException");
781                e.printStackTrace();
782                mStarted = false;
783                initTts();
784            } finally {
785              return result;
786            }
787        }
788    }
789
790
791    /**
792     * Returns whether or not the TTS is busy speaking.
793     *
794     * @return Whether or not the TTS is busy speaking.
795     */
796    public boolean isSpeaking() {
797        synchronized (mStartLock) {
798            if (!mStarted) {
799                return false;
800            }
801            try {
802                return mITts.isSpeaking();
803            } catch (RemoteException e) {
804                // TTS died; restart it.
805                Log.e("TextToSpeech.java - isSpeaking", "RemoteException");
806                e.printStackTrace();
807                mStarted = false;
808                initTts();
809            } catch (NullPointerException e) {
810                // TTS died; restart it.
811                Log.e("TextToSpeech.java - isSpeaking", "NullPointerException");
812                e.printStackTrace();
813                mStarted = false;
814                initTts();
815            } catch (IllegalStateException e) {
816                // TTS died; restart it.
817                Log.e("TextToSpeech.java - isSpeaking", "IllegalStateException");
818                e.printStackTrace();
819                mStarted = false;
820                initTts();
821            }
822            return false;
823        }
824    }
825
826
827    /**
828     * Stops speech from the TTS.
829     *
830     * @return Code indicating success or failure. See {@link #ERROR} and {@link #SUCCESS}.
831     */
832    public int stop() {
833        synchronized (mStartLock) {
834            int result = ERROR;
835            if (!mStarted) {
836                return result;
837            }
838            try {
839                result = mITts.stop(mPackageName);
840            } catch (RemoteException e) {
841                // TTS died; restart it.
842                Log.e("TextToSpeech.java - stop", "RemoteException");
843                e.printStackTrace();
844                mStarted = false;
845                initTts();
846            } catch (NullPointerException e) {
847                // TTS died; restart it.
848                Log.e("TextToSpeech.java - stop", "NullPointerException");
849                e.printStackTrace();
850                mStarted = false;
851                initTts();
852            } catch (IllegalStateException e) {
853                // TTS died; restart it.
854                Log.e("TextToSpeech.java - stop", "IllegalStateException");
855                e.printStackTrace();
856                mStarted = false;
857                initTts();
858            } finally {
859              return result;
860            }
861        }
862    }
863
864
865    /**
866     * Sets the speech rate for the TTS engine.
867     *
868     * Note that the speech rate is not universally supported by all engines and
869     * will be treated as a hint. The TTS library will try to use the specified
870     * speech rate, but there is no guarantee.
871     * This has no effect on any pre-recorded speech.
872     *
873     * @param speechRate
874     *            The speech rate for the TTS engine. 1 is the normal speed,
875     *            lower values slow down the speech (0.5 is half the normal speech rate),
876     *            greater values accelerate it (2 is twice the normal speech rate).
877     *
878     * @return Code indicating success or failure. See {@link #ERROR} and {@link #SUCCESS}.
879     */
880    public int setSpeechRate(float speechRate) {
881        synchronized (mStartLock) {
882            int result = ERROR;
883            if (!mStarted) {
884                return result;
885            }
886            try {
887                if (speechRate > 0) {
888                    int rate = (int)(speechRate*100);
889                    mCachedParams[Engine.PARAM_POSITION_RATE + 1] = String.valueOf(rate);
890                    // the rate is not set here, instead it is cached so it will be associated
891                    // with all upcoming utterances.
892                    if (speechRate > 0.0f) {
893                        result = SUCCESS;
894                    } else {
895                        result = ERROR;
896                    }
897                }
898            } catch (NullPointerException e) {
899                // TTS died; restart it.
900                Log.e("TextToSpeech.java - setSpeechRate", "NullPointerException");
901                e.printStackTrace();
902                mStarted = false;
903                initTts();
904            } catch (IllegalStateException e) {
905                // TTS died; restart it.
906                Log.e("TextToSpeech.java - setSpeechRate", "IllegalStateException");
907                e.printStackTrace();
908                mStarted = false;
909                initTts();
910            } finally {
911              return result;
912            }
913        }
914    }
915
916
917    /**
918     * Sets the speech pitch for the TTS engine.
919     *
920     * Note that the pitch is not universally supported by all engines and
921     * will be treated as a hint. The TTS library will try to use the specified
922     * pitch, but there is no guarantee.
923     * This has no effect on any pre-recorded speech.
924     *
925     * @param pitch
926     *            The pitch for the TTS engine. 1 is the normal pitch,
927     *            lower values lower the tone of the synthesized voice,
928     *            greater values increase it.
929     *
930     * @return Code indicating success or failure. See {@link #ERROR} and {@link #SUCCESS}.
931     */
932    public int setPitch(float pitch) {
933        synchronized (mStartLock) {
934            int result = ERROR;
935            if (!mStarted) {
936                return result;
937            }
938            try {
939                if (pitch > 0) {
940                    result = mITts.setPitch(mPackageName, (int)(pitch*100));
941                }
942            } catch (RemoteException e) {
943                // TTS died; restart it.
944                Log.e("TextToSpeech.java - setPitch", "RemoteException");
945                e.printStackTrace();
946                mStarted = false;
947                initTts();
948            } catch (NullPointerException e) {
949                // TTS died; restart it.
950                Log.e("TextToSpeech.java - setPitch", "NullPointerException");
951                e.printStackTrace();
952                mStarted = false;
953                initTts();
954            } catch (IllegalStateException e) {
955                // TTS died; restart it.
956                Log.e("TextToSpeech.java - setPitch", "IllegalStateException");
957                e.printStackTrace();
958                mStarted = false;
959                initTts();
960            } finally {
961              return result;
962            }
963        }
964    }
965
966
967    /**
968     * Sets the language for the TTS engine.
969     *
970     * Note that the language is not universally supported by all engines and
971     * will be treated as a hint. The TTS library will try to use the specified
972     * language as represented by the Locale, but there is no guarantee.
973     *
974     * @param loc
975     *            The locale describing the language to be used.
976     *
977     * @return code indicating the support status for the locale. See {@link #LANG_AVAILABLE},
978     *         {@link #LANG_COUNTRY_AVAILABLE}, {@link #LANG_COUNTRY_VAR_AVAILABLE},
979     *         {@link #LANG_MISSING_DATA} and {@link #LANG_NOT_SUPPORTED}.
980     */
981    public int setLanguage(Locale loc) {
982        synchronized (mStartLock) {
983            int result = LANG_NOT_SUPPORTED;
984            if (!mStarted) {
985                return result;
986            }
987            try {
988                mCachedParams[Engine.PARAM_POSITION_LANGUAGE + 1] = loc.getISO3Language();
989                mCachedParams[Engine.PARAM_POSITION_COUNTRY + 1] = loc.getISO3Country();
990                mCachedParams[Engine.PARAM_POSITION_VARIANT + 1] = loc.getVariant();
991                // the language is not set here, instead it is cached so it will be associated
992                // with all upcoming utterances. But we still need to report the language support,
993                // which is achieved by calling isLanguageAvailable()
994                result = mITts.isLanguageAvailable(
995                        mCachedParams[Engine.PARAM_POSITION_LANGUAGE + 1],
996                        mCachedParams[Engine.PARAM_POSITION_COUNTRY + 1],
997                        mCachedParams[Engine.PARAM_POSITION_VARIANT + 1] );
998            } catch (RemoteException e) {
999                // TTS died; restart it.
1000                Log.e("TextToSpeech.java - setLanguage", "RemoteException");
1001                e.printStackTrace();
1002                mStarted = false;
1003                initTts();
1004            } catch (NullPointerException e) {
1005                // TTS died; restart it.
1006                Log.e("TextToSpeech.java - setLanguage", "NullPointerException");
1007                e.printStackTrace();
1008                mStarted = false;
1009                initTts();
1010            } catch (IllegalStateException e) {
1011                // TTS died; restart it.
1012                Log.e("TextToSpeech.java - setLanguage", "IllegalStateException");
1013                e.printStackTrace();
1014                mStarted = false;
1015                initTts();
1016            } finally {
1017              return result;
1018            }
1019        }
1020    }
1021
1022
1023    /**
1024     * Returns a Locale instance describing the language currently being used by the TTS engine.
1025     * @return language, country (if any) and variant (if any) used by the engine stored in a Locale
1026     *     instance, or null is the TTS engine has failed.
1027     */
1028    public Locale getLanguage() {
1029        synchronized (mStartLock) {
1030            if (!mStarted) {
1031                return null;
1032            }
1033            try {
1034                String[] locStrings =  mITts.getLanguage();
1035                if (locStrings.length == 3) {
1036                    return new Locale(locStrings[0], locStrings[1], locStrings[2]);
1037                } else {
1038                    return null;
1039                }
1040            } catch (RemoteException e) {
1041                // TTS died; restart it.
1042                Log.e("TextToSpeech.java - getLanguage", "RemoteException");
1043                e.printStackTrace();
1044                mStarted = false;
1045                initTts();
1046            } catch (NullPointerException e) {
1047                // TTS died; restart it.
1048                Log.e("TextToSpeech.java - getLanguage", "NullPointerException");
1049                e.printStackTrace();
1050                mStarted = false;
1051                initTts();
1052            } catch (IllegalStateException e) {
1053                // TTS died; restart it.
1054                Log.e("TextToSpeech.java - getLanguage", "IllegalStateException");
1055                e.printStackTrace();
1056                mStarted = false;
1057                initTts();
1058            }
1059            return null;
1060        }
1061    }
1062
1063    /**
1064     * Checks if the specified language as represented by the Locale is available.
1065     *
1066     * @param loc
1067     *            The Locale describing the language to be used.
1068     *
1069     * @return code indicating the support status for the locale. See {@link #LANG_AVAILABLE},
1070     *         {@link #LANG_COUNTRY_AVAILABLE}, {@link #LANG_COUNTRY_VAR_AVAILABLE},
1071     *         {@link #LANG_MISSING_DATA} and {@link #LANG_NOT_SUPPORTED}.
1072     */
1073    public int isLanguageAvailable(Locale loc) {
1074        synchronized (mStartLock) {
1075            int result = LANG_NOT_SUPPORTED;
1076            if (!mStarted) {
1077                return result;
1078            }
1079            try {
1080                result = mITts.isLanguageAvailable(loc.getISO3Language(),
1081                        loc.getISO3Country(), loc.getVariant());
1082            } catch (RemoteException e) {
1083                // TTS died; restart it.
1084                Log.e("TextToSpeech.java - isLanguageAvailable", "RemoteException");
1085                e.printStackTrace();
1086                mStarted = false;
1087                initTts();
1088            } catch (NullPointerException e) {
1089                // TTS died; restart it.
1090                Log.e("TextToSpeech.java - isLanguageAvailable", "NullPointerException");
1091                e.printStackTrace();
1092                mStarted = false;
1093                initTts();
1094            } catch (IllegalStateException e) {
1095                // TTS died; restart it.
1096                Log.e("TextToSpeech.java - isLanguageAvailable", "IllegalStateException");
1097                e.printStackTrace();
1098                mStarted = false;
1099                initTts();
1100            } finally {
1101              return result;
1102            }
1103        }
1104    }
1105
1106
1107    /**
1108     * Synthesizes the given text to a file using the specified parameters.
1109     *
1110     * @param text
1111     *            The String of text that should be synthesized
1112     * @param params
1113     *            A hashmap of parameters.
1114     * @param filename
1115     *            The string that gives the full output filename; it should be
1116     *            something like "/sdcard/myappsounds/mysound.wav".
1117     *
1118     * @return Code indicating success or failure. See {@link #ERROR} and {@link #SUCCESS}.
1119     */
1120    public int synthesizeToFile(String text, HashMap<String,String> params,
1121            String filename) {
1122        synchronized (mStartLock) {
1123            int result = ERROR;
1124            if (!mStarted) {
1125                return result;
1126            }
1127            try {
1128                if ((params != null) && (!params.isEmpty())) {
1129                    // no need to read the stream type here
1130                    String extra = params.get(Engine.KEY_PARAM_UTTERANCE_ID);
1131                    if (extra != null) {
1132                        mCachedParams[Engine.PARAM_POSITION_UTTERANCE_ID + 1] = extra;
1133                    }
1134                }
1135                if (mITts.synthesizeToFile(mPackageName, text, mCachedParams, filename)){
1136                    result = SUCCESS;
1137                }
1138            } catch (RemoteException e) {
1139                // TTS died; restart it.
1140                Log.e("TextToSpeech.java - synthesizeToFile", "RemoteException");
1141                e.printStackTrace();
1142                mStarted = false;
1143                initTts();
1144            } catch (NullPointerException e) {
1145                // TTS died; restart it.
1146                Log.e("TextToSpeech.java - synthesizeToFile", "NullPointerException");
1147                e.printStackTrace();
1148                mStarted = false;
1149                initTts();
1150            } catch (IllegalStateException e) {
1151                // TTS died; restart it.
1152                Log.e("TextToSpeech.java - synthesizeToFile", "IllegalStateException");
1153                e.printStackTrace();
1154                mStarted = false;
1155                initTts();
1156            } finally {
1157                resetCachedParams();
1158                return result;
1159            }
1160        }
1161    }
1162
1163
1164    /**
1165     * Convenience method to reset the cached parameters to the current default values
1166     * if they are not persistent between calls to the service.
1167     */
1168    private void resetCachedParams() {
1169        mCachedParams[Engine.PARAM_POSITION_STREAM + 1] =
1170                String.valueOf(Engine.DEFAULT_STREAM);
1171        mCachedParams[Engine.PARAM_POSITION_UTTERANCE_ID+ 1] = "";
1172    }
1173
1174    /**
1175     * Sets the OnUtteranceCompletedListener that will fire when an utterance completes.
1176     *
1177     * @param listener
1178     *            The OnUtteranceCompletedListener
1179     *
1180     * @return Code indicating success or failure. See {@link #ERROR} and {@link #SUCCESS}.
1181     */
1182    public int setOnUtteranceCompletedListener(
1183            final OnUtteranceCompletedListener listener) {
1184        synchronized (mStartLock) {
1185            int result = ERROR;
1186            if (!mStarted) {
1187                return result;
1188            }
1189            mITtscallback = new ITtsCallback.Stub() {
1190                public void utteranceCompleted(String utteranceId) throws RemoteException {
1191                    if (listener != null) {
1192                        listener.onUtteranceCompleted(utteranceId);
1193                    }
1194                }
1195            };
1196            try {
1197                result = mITts.registerCallback(mPackageName, mITtscallback);
1198            } catch (RemoteException e) {
1199                // TTS died; restart it.
1200                Log.e("TextToSpeech.java - registerCallback", "RemoteException");
1201                e.printStackTrace();
1202                mStarted = false;
1203                initTts();
1204            } catch (NullPointerException e) {
1205                // TTS died; restart it.
1206                Log.e("TextToSpeech.java - registerCallback", "NullPointerException");
1207                e.printStackTrace();
1208                mStarted = false;
1209                initTts();
1210            } catch (IllegalStateException e) {
1211                // TTS died; restart it.
1212                Log.e("TextToSpeech.java - registerCallback", "IllegalStateException");
1213                e.printStackTrace();
1214                mStarted = false;
1215                initTts();
1216            } finally {
1217                return result;
1218            }
1219        }
1220    }
1221
1222}
1223