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