TextToSpeech.java revision 4b73867a12a9339c7788e8949aac4a32d2eee22b
1/*
2 * Copyright (C) 2009 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5 * use this file except in compliance with the License. You may obtain a copy of
6 * the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13 * License for the specific language governing permissions and limitations under
14 * the License.
15 */
16package android.speech.tts;
17
18import android.annotation.SdkConstant;
19import android.annotation.SdkConstant.SdkConstantType;
20import android.content.ComponentName;
21import android.content.ContentResolver;
22import android.content.Context;
23import android.content.Intent;
24import android.content.ServiceConnection;
25import android.media.AudioAttributes;
26import android.media.AudioManager;
27import android.net.Uri;
28import android.os.AsyncTask;
29import android.os.Bundle;
30import android.os.IBinder;
31import android.os.ParcelFileDescriptor;
32import android.os.RemoteException;
33import android.provider.Settings;
34import android.text.TextUtils;
35import android.util.Log;
36
37import java.io.File;
38import java.io.FileNotFoundException;
39import java.io.IOException;
40import java.util.ArrayList;
41import java.util.Collections;
42import java.util.HashMap;
43import java.util.HashSet;
44import java.util.List;
45import java.util.Locale;
46import java.util.Map;
47import java.util.MissingResourceException;
48import java.util.Set;
49
50/**
51 *
52 * Synthesizes speech from text for immediate playback or to create a sound file.
53 * <p>A TextToSpeech instance can only be used to synthesize text once it has completed its
54 * initialization. Implement the {@link TextToSpeech.OnInitListener} to be
55 * notified of the completion of the initialization.<br>
56 * When you are done using the TextToSpeech instance, call the {@link #shutdown()} method
57 * to release the native resources used by the TextToSpeech engine.
58 */
59public class TextToSpeech {
60
61    private static final String TAG = "TextToSpeech";
62
63    /**
64     * Denotes a successful operation.
65     */
66    public static final int SUCCESS = 0;
67    /**
68     * Denotes a generic operation failure.
69     */
70    public static final int ERROR = -1;
71
72    /**
73     * Denotes a stop requested by a client. It's used only on the service side of the API,
74     * client should never expect to see this result code.
75     */
76    public static final int STOPPED = -2;
77
78    /**
79     * Denotes a failure of a TTS engine to synthesize the given input.
80     */
81    public static final int ERROR_SYNTHESIS = -3;
82
83    /**
84     * Denotes a failure of a TTS service.
85     */
86    public static final int ERROR_SERVICE = -4;
87
88    /**
89     * Denotes a failure related to the output (audio device or a file).
90     */
91    public static final int ERROR_OUTPUT = -5;
92
93    /**
94     * Denotes a failure caused by a network connectivity problems.
95     */
96    public static final int ERROR_NETWORK = -6;
97
98    /**
99     * Denotes a failure caused by network timeout.
100     */
101    public static final int ERROR_NETWORK_TIMEOUT = -7;
102
103    /**
104     * Denotes a failure caused by an invalid request.
105     */
106    public static final int ERROR_INVALID_REQUEST = -8;
107
108    /**
109     * Denotes a failure caused by an unfinished download of the voice data.
110     * @see Engine#KEY_FEATURE_NOT_INSTALLED
111     */
112    public static final int ERROR_NOT_INSTALLED_YET = -9;
113
114    /**
115     * Queue mode where all entries in the playback queue (media to be played
116     * and text to be synthesized) are dropped and replaced by the new entry.
117     * Queues are flushed with respect to a given calling app. Entries in the queue
118     * from other callees are not discarded.
119     */
120    public static final int QUEUE_FLUSH = 0;
121    /**
122     * Queue mode where the new entry is added at the end of the playback queue.
123     */
124    public static final int QUEUE_ADD = 1;
125    /**
126     * Queue mode where the entire playback queue is purged. This is different
127     * from {@link #QUEUE_FLUSH} in that all entries are purged, not just entries
128     * from a given caller.
129     *
130     * @hide
131     */
132    static final int QUEUE_DESTROY = 2;
133
134    /**
135     * Denotes the language is available exactly as specified by the locale.
136     */
137    public static final int LANG_COUNTRY_VAR_AVAILABLE = 2;
138
139    /**
140     * Denotes the language is available for the language and country specified
141     * by the locale, but not the variant.
142     */
143    public static final int LANG_COUNTRY_AVAILABLE = 1;
144
145    /**
146     * Denotes the language is available for the language by the locale,
147     * but not the country and variant.
148     */
149    public static final int LANG_AVAILABLE = 0;
150
151    /**
152     * Denotes the language data is missing.
153     */
154    public static final int LANG_MISSING_DATA = -1;
155
156    /**
157     * Denotes the language is not supported.
158     */
159    public static final int LANG_NOT_SUPPORTED = -2;
160
161    /**
162     * Broadcast Action: The TextToSpeech synthesizer has completed processing
163     * of all the text in the speech queue.
164     *
165     * Note that this notifies callers when the <b>engine</b> has finished has
166     * processing text data. Audio playback might not have completed (or even started)
167     * at this point. If you wish to be notified when this happens, see
168     * {@link OnUtteranceCompletedListener}.
169     */
170    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
171    public static final String ACTION_TTS_QUEUE_PROCESSING_COMPLETED =
172            "android.speech.tts.TTS_QUEUE_PROCESSING_COMPLETED";
173
174    /**
175     * Interface definition of a callback to be invoked indicating the completion of the
176     * TextToSpeech engine initialization.
177     */
178    public interface OnInitListener {
179        /**
180         * Called to signal the completion of the TextToSpeech engine initialization.
181         *
182         * @param status {@link TextToSpeech#SUCCESS} or {@link TextToSpeech#ERROR}.
183         */
184        public void onInit(int status);
185    }
186
187    /**
188     * Listener that will be called when the TTS service has
189     * completed synthesizing an utterance. This is only called if the utterance
190     * has an utterance ID (see {@link TextToSpeech.Engine#KEY_PARAM_UTTERANCE_ID}).
191     *
192     * @deprecated Use {@link UtteranceProgressListener} instead.
193     */
194    @Deprecated
195    public interface OnUtteranceCompletedListener {
196        /**
197         * Called when an utterance has been synthesized.
198         *
199         * @param utteranceId the identifier of the utterance.
200         */
201        public void onUtteranceCompleted(String utteranceId);
202    }
203
204    /**
205     * Constants and parameter names for controlling text-to-speech. These include:
206     *
207     * <ul>
208     *     <li>
209     *         Intents to ask engine to install data or check its data and
210     *         extras for a TTS engine's check data activity.
211     *     </li>
212     *     <li>
213     *         Keys for the parameters passed with speak commands, e.g.
214     *         {@link Engine#KEY_PARAM_UTTERANCE_ID}, {@link Engine#KEY_PARAM_STREAM}.
215     *     </li>
216     *     <li>
217     *         A list of feature strings that engines might support, e.g
218     *         {@link Engine#KEY_FEATURE_NETWORK_SYNTHESIS}). These values may be passed in to
219     *         {@link TextToSpeech#speak} and {@link TextToSpeech#synthesizeToFile} to modify
220     *         engine behaviour. The engine can be queried for the set of features it supports
221     *         through {@link TextToSpeech#getFeatures(java.util.Locale)}.
222     *     </li>
223     * </ul>
224     */
225    public class Engine {
226
227        /**
228         * Default speech rate.
229         * @hide
230         */
231        public static final int DEFAULT_RATE = 100;
232
233        /**
234         * Default pitch.
235         * @hide
236         */
237        public static final int DEFAULT_PITCH = 100;
238
239        /**
240         * Default volume.
241         * @hide
242         */
243        public static final float DEFAULT_VOLUME = 1.0f;
244
245        /**
246         * Default pan (centered).
247         * @hide
248         */
249        public static final float DEFAULT_PAN = 0.0f;
250
251        /**
252         * Default value for {@link Settings.Secure#TTS_USE_DEFAULTS}.
253         * @hide
254         */
255        public static final int USE_DEFAULTS = 0; // false
256
257        /**
258         * Package name of the default TTS engine.
259         *
260         * @hide
261         * @deprecated No longer in use, the default engine is determined by
262         *         the sort order defined in {@link TtsEngines}. Note that
263         *         this doesn't "break" anything because there is no guarantee that
264         *         the engine specified below is installed on a given build, let
265         *         alone be the default.
266         */
267        @Deprecated
268        public static final String DEFAULT_ENGINE = "com.svox.pico";
269
270        /**
271         * Default audio stream used when playing synthesized speech.
272         */
273        public static final int DEFAULT_STREAM = AudioManager.STREAM_MUSIC;
274
275        /**
276         * Indicates success when checking the installation status of the resources used by the
277         * TextToSpeech engine with the {@link #ACTION_CHECK_TTS_DATA} intent.
278         */
279        public static final int CHECK_VOICE_DATA_PASS = 1;
280
281        /**
282         * Indicates failure when checking the installation status of the resources used by the
283         * TextToSpeech engine with the {@link #ACTION_CHECK_TTS_DATA} intent.
284         */
285        public static final int CHECK_VOICE_DATA_FAIL = 0;
286
287        /**
288         * Indicates erroneous data when checking the installation status of the resources used by
289         * the TextToSpeech engine with the {@link #ACTION_CHECK_TTS_DATA} intent.
290         *
291         * @deprecated Use CHECK_VOICE_DATA_FAIL instead.
292         */
293        @Deprecated
294        public static final int CHECK_VOICE_DATA_BAD_DATA = -1;
295
296        /**
297         * Indicates missing resources when checking the installation status of the resources used
298         * by the TextToSpeech engine with the {@link #ACTION_CHECK_TTS_DATA} intent.
299         *
300         * @deprecated Use CHECK_VOICE_DATA_FAIL instead.
301         */
302        @Deprecated
303        public static final int CHECK_VOICE_DATA_MISSING_DATA = -2;
304
305        /**
306         * Indicates missing storage volume when checking the installation status of the resources
307         * used by the TextToSpeech engine with the {@link #ACTION_CHECK_TTS_DATA} intent.
308         *
309         * @deprecated Use CHECK_VOICE_DATA_FAIL instead.
310         */
311        @Deprecated
312        public static final int CHECK_VOICE_DATA_MISSING_VOLUME = -3;
313
314        /**
315         * Intent for starting a TTS service. Services that handle this intent must
316         * extend {@link TextToSpeechService}. Normal applications should not use this intent
317         * directly, instead they should talk to the TTS service using the the methods in this
318         * class.
319         */
320        @SdkConstant(SdkConstantType.SERVICE_ACTION)
321        public static final String INTENT_ACTION_TTS_SERVICE =
322                "android.intent.action.TTS_SERVICE";
323
324        /**
325         * Name under which a text to speech engine publishes information about itself.
326         * This meta-data should reference an XML resource containing a
327         * <code>&lt;{@link android.R.styleable#TextToSpeechEngine tts-engine}&gt;</code>
328         * tag.
329         */
330        public static final String SERVICE_META_DATA = "android.speech.tts";
331
332        // intents to ask engine to install data or check its data
333        /**
334         * Activity Action: Triggers the platform TextToSpeech engine to
335         * start the activity that installs the resource files on the device
336         * that are required for TTS to be operational. Since the installation
337         * of the data can be interrupted or declined by the user, the application
338         * shouldn't expect successful installation upon return from that intent,
339         * and if need be, should check installation status with
340         * {@link #ACTION_CHECK_TTS_DATA}.
341         */
342        @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
343        public static final String ACTION_INSTALL_TTS_DATA =
344                "android.speech.tts.engine.INSTALL_TTS_DATA";
345
346        /**
347         * Broadcast Action: broadcast to signal the change in the list of available
348         * languages or/and their features.
349         */
350        @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
351        public static final String ACTION_TTS_DATA_INSTALLED =
352                "android.speech.tts.engine.TTS_DATA_INSTALLED";
353
354        /**
355         * Activity Action: Starts the activity from the platform TextToSpeech
356         * engine to verify the proper installation and availability of the
357         * resource files on the system. Upon completion, the activity will
358         * return one of the following codes:
359         * {@link #CHECK_VOICE_DATA_PASS},
360         * {@link #CHECK_VOICE_DATA_FAIL},
361         * <p> Moreover, the data received in the activity result will contain the following
362         * fields:
363         * <ul>
364         *   <li>{@link #EXTRA_AVAILABLE_VOICES} which contains an ArrayList<String> of all the
365         *   available voices. The format of each voice is: lang-COUNTRY-variant where COUNTRY and
366         *   variant are optional (ie, "eng" or "eng-USA" or "eng-USA-FEMALE").</li>
367         *   <li>{@link #EXTRA_UNAVAILABLE_VOICES} which contains an ArrayList<String> of all the
368         *   unavailable voices (ones that user can install). The format of each voice is:
369         *   lang-COUNTRY-variant where COUNTRY and variant are optional (ie, "eng" or
370         *   "eng-USA" or "eng-USA-FEMALE").</li>
371         * </ul>
372         */
373        @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
374        public static final String ACTION_CHECK_TTS_DATA =
375                "android.speech.tts.engine.CHECK_TTS_DATA";
376
377        /**
378         * Activity intent for getting some sample text to use for demonstrating TTS. Specific
379         * locale have to be requested by passing following extra parameters:
380         * <ul>
381         *   <li>language</li>
382         *   <li>country</li>
383         *   <li>variant</li>
384         * </ul>
385         *
386         * Upon completion, the activity result may contain the following fields:
387         * <ul>
388         *   <li>{@link #EXTRA_SAMPLE_TEXT} which contains an String with sample text.</li>
389         * </ul>
390         */
391        @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
392        public static final String ACTION_GET_SAMPLE_TEXT =
393                "android.speech.tts.engine.GET_SAMPLE_TEXT";
394
395        /**
396         * Extra information received with the {@link #ACTION_GET_SAMPLE_TEXT} intent result where
397         * the TextToSpeech engine returns an String with sample text for requested voice
398         */
399        public static final String EXTRA_SAMPLE_TEXT = "sampleText";
400
401
402        // extras for a TTS engine's check data activity
403        /**
404         * Extra information received with the {@link #ACTION_CHECK_TTS_DATA} intent result where
405         * the TextToSpeech engine returns an ArrayList<String> of all the available voices.
406         * The format of each voice is: lang-COUNTRY-variant where COUNTRY and variant are
407         * optional (ie, "eng" or "eng-USA" or "eng-USA-FEMALE").
408         */
409        public static final String EXTRA_AVAILABLE_VOICES = "availableVoices";
410
411        /**
412         * Extra information received with the {@link #ACTION_CHECK_TTS_DATA} intent result where
413         * the TextToSpeech engine returns an ArrayList<String> of all the unavailable voices.
414         * The format of each voice is: lang-COUNTRY-variant where COUNTRY and variant are
415         * optional (ie, "eng" or "eng-USA" or "eng-USA-FEMALE").
416         */
417        public static final String EXTRA_UNAVAILABLE_VOICES = "unavailableVoices";
418
419        /**
420         * Extra information received with the {@link #ACTION_CHECK_TTS_DATA} intent result where
421         * the TextToSpeech engine specifies the path to its resources.
422         *
423         * It may be used by language packages to find out where to put their data.
424         *
425         * @deprecated TTS engine implementation detail, this information has no use for
426         * text-to-speech API client.
427         */
428        @Deprecated
429        public static final String EXTRA_VOICE_DATA_ROOT_DIRECTORY = "dataRoot";
430
431        /**
432         * Extra information received with the {@link #ACTION_CHECK_TTS_DATA} intent result where
433         * the TextToSpeech engine specifies the file names of its resources under the
434         * resource path.
435         *
436         * @deprecated TTS engine implementation detail, this information has no use for
437         * text-to-speech API client.
438         */
439        @Deprecated
440        public static final String EXTRA_VOICE_DATA_FILES = "dataFiles";
441
442        /**
443         * Extra information received with the {@link #ACTION_CHECK_TTS_DATA} intent result where
444         * the TextToSpeech engine specifies the locale associated with each resource file.
445         *
446         * @deprecated TTS engine implementation detail, this information has no use for
447         * text-to-speech API client.
448         */
449        @Deprecated
450        public static final String EXTRA_VOICE_DATA_FILES_INFO = "dataFilesInfo";
451
452        /**
453         * Extra information sent with the {@link #ACTION_CHECK_TTS_DATA} intent where the
454         * caller indicates to the TextToSpeech engine which specific sets of voice data to
455         * check for by sending an ArrayList<String> of the voices that are of interest.
456         * The format of each voice is: lang-COUNTRY-variant where COUNTRY and variant are
457         * optional (ie, "eng" or "eng-USA" or "eng-USA-FEMALE").
458         *
459         * @deprecated Redundant functionality, checking for existence of specific sets of voice
460         * data can be done on client side.
461         */
462        @Deprecated
463        public static final String EXTRA_CHECK_VOICE_DATA_FOR = "checkVoiceDataFor";
464
465        // extras for a TTS engine's data installation
466        /**
467         * Extra information received with the {@link #ACTION_TTS_DATA_INSTALLED} intent result.
468         * It indicates whether the data files for the synthesis engine were successfully
469         * installed. The installation was initiated with the  {@link #ACTION_INSTALL_TTS_DATA}
470         * intent. The possible values for this extra are
471         * {@link TextToSpeech#SUCCESS} and {@link TextToSpeech#ERROR}.
472         *
473         * @deprecated No longer in use. If client ise interested in information about what
474         * changed, is should send ACTION_CHECK_TTS_DATA intent to discover available voices.
475         */
476        @Deprecated
477        public static final String EXTRA_TTS_DATA_INSTALLED = "dataInstalled";
478
479        // keys for the parameters passed with speak commands. Hidden keys are used internally
480        // to maintain engine state for each TextToSpeech instance.
481        /**
482         * @hide
483         */
484        public static final String KEY_PARAM_RATE = "rate";
485
486        /**
487         * @hide
488         */
489        public static final String KEY_PARAM_VOICE_NAME = "voiceName";
490
491        /**
492         * @hide
493         */
494        public static final String KEY_PARAM_LANGUAGE = "language";
495
496        /**
497         * @hide
498         */
499        public static final String KEY_PARAM_COUNTRY = "country";
500
501        /**
502         * @hide
503         */
504        public static final String KEY_PARAM_VARIANT = "variant";
505
506        /**
507         * @hide
508         */
509        public static final String KEY_PARAM_ENGINE = "engine";
510
511        /**
512         * @hide
513         */
514        public static final String KEY_PARAM_PITCH = "pitch";
515
516        /**
517         * Parameter key to specify the audio stream type to be used when speaking text
518         * or playing back a file. The value should be one of the STREAM_ constants
519         * defined in {@link AudioManager}.
520         *
521         * @see TextToSpeech#speak(String, int, HashMap)
522         * @see TextToSpeech#playEarcon(String, int, HashMap)
523         */
524        public static final String KEY_PARAM_STREAM = "streamType";
525
526        /**
527         * Parameter key to specify the audio attributes to be used when
528         * speaking text or playing back a file. The value should be set
529         * using {@link TextToSpeech#setAudioAttributes(AudioAttributes)}.
530         *
531         * @see TextToSpeech#speak(String, int, HashMap)
532         * @see TextToSpeech#playEarcon(String, int, HashMap)
533         * @hide
534         */
535        public static final String KEY_PARAM_AUDIO_ATTRIBUTES = "audioAttributes";
536
537        /**
538         * Parameter key to identify an utterance in the
539         * {@link TextToSpeech.OnUtteranceCompletedListener} after text has been
540         * spoken, a file has been played back or a silence duration has elapsed.
541         *
542         * @see TextToSpeech#speak(String, int, HashMap)
543         * @see TextToSpeech#playEarcon(String, int, HashMap)
544         * @see TextToSpeech#synthesizeToFile(String, HashMap, String)
545         */
546        public static final String KEY_PARAM_UTTERANCE_ID = "utteranceId";
547
548        /**
549         * Parameter key to specify the speech volume relative to the current stream type
550         * volume used when speaking text. Volume is specified as a float ranging from 0 to 1
551         * where 0 is silence, and 1 is the maximum volume (the default behavior).
552         *
553         * @see TextToSpeech#speak(String, int, HashMap)
554         * @see TextToSpeech#playEarcon(String, int, HashMap)
555         */
556        public static final String KEY_PARAM_VOLUME = "volume";
557
558        /**
559         * Parameter key to specify how the speech is panned from left to right when speaking text.
560         * Pan is specified as a float ranging from -1 to +1 where -1 maps to a hard-left pan,
561         * 0 to center (the default behavior), and +1 to hard-right.
562         *
563         * @see TextToSpeech#speak(String, int, HashMap)
564         * @see TextToSpeech#playEarcon(String, int, HashMap)
565         */
566        public static final String KEY_PARAM_PAN = "pan";
567
568        /**
569         * Feature key for network synthesis. See {@link TextToSpeech#getFeatures(Locale)}
570         * for a description of how feature keys work. If set (and supported by the engine
571         * as per {@link TextToSpeech#getFeatures(Locale)}, the engine must
572         * use network based synthesis.
573         *
574         * @see TextToSpeech#speak(String, int, java.util.HashMap)
575         * @see TextToSpeech#synthesizeToFile(String, java.util.HashMap, String)
576         * @see TextToSpeech#getFeatures(java.util.Locale)
577         *
578         * @deprecated Starting from API level 21, to select network synthesis, call
579         * ({@link TextToSpeech#getVoices()}, find a suitable network voice
580         * ({@link Voice#isNetworkConnectionRequired()}) and pass it
581         * to {@link TextToSpeech#setVoice(Voice)}).
582         */
583        @Deprecated
584        public static final String KEY_FEATURE_NETWORK_SYNTHESIS = "networkTts";
585
586        /**
587         * Feature key for embedded synthesis. See {@link TextToSpeech#getFeatures(Locale)}
588         * for a description of how feature keys work. If set and supported by the engine
589         * as per {@link TextToSpeech#getFeatures(Locale)}, the engine must synthesize
590         * text on-device (without making network requests).
591         *
592         * @see TextToSpeech#speak(String, int, java.util.HashMap)
593         * @see TextToSpeech#synthesizeToFile(String, java.util.HashMap, String)
594         * @see TextToSpeech#getFeatures(java.util.Locale)
595
596         * @deprecated Starting from API level 21, to select embedded synthesis, call
597         * ({@link TextToSpeech#getVoices()}, find a suitable embedded voice
598         * ({@link Voice#isNetworkConnectionRequired()}) and pass it
599         * to {@link TextToSpeech#setVoice(Voice)}).
600         */
601        @Deprecated
602        public static final String KEY_FEATURE_EMBEDDED_SYNTHESIS = "embeddedTts";
603
604        /**
605         * Parameter key to specify an audio session identifier (obtained from
606         * {@link AudioManager#generateAudioSessionId()}) that will be used by the request audio
607         * output. It can be used to associate one of the {@link android.media.audiofx.AudioEffect}
608         * objects with the synthesis (or earcon) output.
609         *
610         * @see TextToSpeech#speak(String, int, HashMap)
611         * @see TextToSpeech#playEarcon(String, int, HashMap)
612         */
613        public static final String KEY_PARAM_SESSION_ID = "sessionId";
614
615        /**
616         * Feature key that indicates that the voice may need to download additional data to be fully
617         * functional. The download will be triggered by calling
618         * {@link TextToSpeech#setVoice(Voice)} or {@link TextToSpeech#setLanguage(Locale)}.
619         * Until download is complete, each synthesis request will either report
620         * {@link TextToSpeech#ERROR_NOT_INSTALLED_YET} error, or use a different voice to synthesize
621         * the request. This feature should NOT be used as a key of a request parameter.
622         *
623         * @see TextToSpeech#getFeatures(java.util.Locale)
624         * @see Voice#getFeatures()
625         */
626        public static final String KEY_FEATURE_NOT_INSTALLED = "notInstalled";
627
628        /**
629         * Feature key that indicate that a network timeout can be set for the request. If set and
630         * supported as per {@link TextToSpeech#getFeatures(Locale)} or {@link Voice#getFeatures()},
631         * it can be used as request parameter to set the maximum allowed time for a single
632         * request attempt, in milliseconds, before synthesis fails. When used as a key of
633         * a request parameter, its value should be a string with an integer value.
634         *
635         * @see TextToSpeech#getFeatures(java.util.Locale)
636         * @see Voice#getFeatures()
637         */
638        public static final String KEY_FEATURE_NETWORK_TIMEOUT_MS = "networkTimeoutMs";
639
640        /**
641         * Feature key that indicates that network request retries count can be set for the request.
642         * If set and supported as per {@link TextToSpeech#getFeatures(Locale)} or
643         * {@link Voice#getFeatures()}, it can be used as a request parameter to set the
644         * number of network request retries that are attempted in case of failure. When used as
645         * a key of a request parameter, its value should be a string with an integer value.
646         *
647         * @see TextToSpeech#getFeatures(java.util.Locale)
648         * @see Voice#getFeatures()
649         */
650        public static final String KEY_FEATURE_NETWORK_RETRIES_COUNT = "networkRetriesCount";
651    }
652
653    private final Context mContext;
654    private Connection mConnectingServiceConnection;
655    private Connection mServiceConnection;
656    private OnInitListener mInitListener;
657    // Written from an unspecified application thread, read from
658    // a binder thread.
659    private volatile UtteranceProgressListener mUtteranceProgressListener;
660    private final Object mStartLock = new Object();
661
662    private String mRequestedEngine;
663    // Whether to initialize this TTS object with the default engine,
664    // if the requested engine is not available. Valid only if mRequestedEngine
665    // is not null. Used only for testing, though potentially useful API wise
666    // too.
667    private final boolean mUseFallback;
668    private final Map<String, Uri> mEarcons;
669    private final Map<CharSequence, Uri> mUtterances;
670    private final Bundle mParams = new Bundle();
671    private final TtsEngines mEnginesHelper;
672    private volatile String mCurrentEngine = null;
673
674    /**
675     * The constructor for the TextToSpeech class, using the default TTS engine.
676     * This will also initialize the associated TextToSpeech engine if it isn't already running.
677     *
678     * @param context
679     *            The context this instance is running in.
680     * @param listener
681     *            The {@link TextToSpeech.OnInitListener} that will be called when the
682     *            TextToSpeech engine has initialized. In a case of a failure the listener
683     *            may be called immediately, before TextToSpeech instance is fully constructed.
684     */
685    public TextToSpeech(Context context, OnInitListener listener) {
686        this(context, listener, null);
687    }
688
689    /**
690     * The constructor for the TextToSpeech class, using the given TTS engine.
691     * This will also initialize the associated TextToSpeech engine if it isn't already running.
692     *
693     * @param context
694     *            The context this instance is running in.
695     * @param listener
696     *            The {@link TextToSpeech.OnInitListener} that will be called when the
697     *            TextToSpeech engine has initialized. In a case of a failure the listener
698     *            may be called immediately, before TextToSpeech instance is fully constructed.
699     * @param engine Package name of the TTS engine to use.
700     */
701    public TextToSpeech(Context context, OnInitListener listener, String engine) {
702        this(context, listener, engine, null, true);
703    }
704
705    /**
706     * Used by the framework to instantiate TextToSpeech objects with a supplied
707     * package name, instead of using {@link android.content.Context#getPackageName()}
708     *
709     * @hide
710     */
711    public TextToSpeech(Context context, OnInitListener listener, String engine,
712            String packageName, boolean useFallback) {
713        mContext = context;
714        mInitListener = listener;
715        mRequestedEngine = engine;
716        mUseFallback = useFallback;
717
718        mEarcons = new HashMap<String, Uri>();
719        mUtterances = new HashMap<CharSequence, Uri>();
720        mUtteranceProgressListener = null;
721
722        mEnginesHelper = new TtsEngines(mContext);
723        initTts();
724    }
725
726    private <R> R runActionNoReconnect(Action<R> action, R errorResult, String method,
727            boolean onlyEstablishedConnection) {
728        return runAction(action, errorResult, method, false, onlyEstablishedConnection);
729    }
730
731    private <R> R runAction(Action<R> action, R errorResult, String method) {
732        return runAction(action, errorResult, method, true, true);
733    }
734
735    private <R> R runAction(Action<R> action, R errorResult, String method,
736            boolean reconnect, boolean onlyEstablishedConnection) {
737        synchronized (mStartLock) {
738            if (mServiceConnection == null) {
739                Log.w(TAG, method + " failed: not bound to TTS engine");
740                return errorResult;
741            }
742            return mServiceConnection.runAction(action, errorResult, method, reconnect,
743                    onlyEstablishedConnection);
744        }
745    }
746
747    private int initTts() {
748        // Step 1: Try connecting to the engine that was requested.
749        if (mRequestedEngine != null) {
750            if (mEnginesHelper.isEngineInstalled(mRequestedEngine)) {
751                if (connectToEngine(mRequestedEngine)) {
752                    mCurrentEngine = mRequestedEngine;
753                    return SUCCESS;
754                } else if (!mUseFallback) {
755                    mCurrentEngine = null;
756                    dispatchOnInit(ERROR);
757                    return ERROR;
758                }
759            } else if (!mUseFallback) {
760                Log.i(TAG, "Requested engine not installed: " + mRequestedEngine);
761                mCurrentEngine = null;
762                dispatchOnInit(ERROR);
763                return ERROR;
764            }
765        }
766
767        // Step 2: Try connecting to the user's default engine.
768        final String defaultEngine = getDefaultEngine();
769        if (defaultEngine != null && !defaultEngine.equals(mRequestedEngine)) {
770            if (connectToEngine(defaultEngine)) {
771                mCurrentEngine = defaultEngine;
772                return SUCCESS;
773            }
774        }
775
776        // Step 3: Try connecting to the highest ranked engine in the
777        // system.
778        final String highestRanked = mEnginesHelper.getHighestRankedEngineName();
779        if (highestRanked != null && !highestRanked.equals(mRequestedEngine) &&
780                !highestRanked.equals(defaultEngine)) {
781            if (connectToEngine(highestRanked)) {
782                mCurrentEngine = highestRanked;
783                return SUCCESS;
784            }
785        }
786
787        // NOTE: The API currently does not allow the caller to query whether
788        // they are actually connected to any engine. This might fail for various
789        // reasons like if the user disables all her TTS engines.
790
791        mCurrentEngine = null;
792        dispatchOnInit(ERROR);
793        return ERROR;
794    }
795
796    private boolean connectToEngine(String engine) {
797        Connection connection = new Connection();
798        Intent intent = new Intent(Engine.INTENT_ACTION_TTS_SERVICE);
799        intent.setPackage(engine);
800        boolean bound = mContext.bindService(intent, connection, Context.BIND_AUTO_CREATE);
801        if (!bound) {
802            Log.e(TAG, "Failed to bind to " + engine);
803            return false;
804        } else {
805            Log.i(TAG, "Sucessfully bound to " + engine);
806            mConnectingServiceConnection = connection;
807            return true;
808        }
809    }
810
811    private void dispatchOnInit(int result) {
812        synchronized (mStartLock) {
813            if (mInitListener != null) {
814                mInitListener.onInit(result);
815                mInitListener = null;
816            }
817        }
818    }
819
820    private IBinder getCallerIdentity() {
821        return mServiceConnection.getCallerIdentity();
822    }
823
824    /**
825     * Releases the resources used by the TextToSpeech engine.
826     * It is good practice for instance to call this method in the onDestroy() method of an Activity
827     * so the TextToSpeech engine can be cleanly stopped.
828     */
829    public void shutdown() {
830        // Special case, we are asked to shutdown connection that did finalize its connection.
831        synchronized (mStartLock) {
832            if (mConnectingServiceConnection != null) {
833                mContext.unbindService(mConnectingServiceConnection);
834                mConnectingServiceConnection = null;
835                return;
836            }
837        }
838
839        // Post connection case
840        runActionNoReconnect(new Action<Void>() {
841            @Override
842            public Void run(ITextToSpeechService service) throws RemoteException {
843                service.setCallback(getCallerIdentity(), null);
844                service.stop(getCallerIdentity());
845                mServiceConnection.disconnect();
846                // Context#unbindService does not result in a call to
847                // ServiceConnection#onServiceDisconnected. As a result, the
848                // service ends up being destroyed (if there are no other open
849                // connections to it) but the process lives on and the
850                // ServiceConnection continues to refer to the destroyed service.
851                //
852                // This leads to tons of log spam about SynthThread being dead.
853                mServiceConnection = null;
854                mCurrentEngine = null;
855                return null;
856            }
857        }, null, "shutdown", false);
858    }
859
860    /**
861     * Adds a mapping between a string of text and a sound resource in a
862     * package. After a call to this method, subsequent calls to
863     * {@link #speak(String, int, HashMap)} will play the specified sound resource
864     * if it is available, or synthesize the text it is missing.
865     *
866     * @param text
867     *            The string of text. Example: <code>"south_south_east"</code>
868     *
869     * @param packagename
870     *            Pass the packagename of the application that contains the
871     *            resource. If the resource is in your own application (this is
872     *            the most common case), then put the packagename of your
873     *            application here.<br/>
874     *            Example: <b>"com.google.marvin.compass"</b><br/>
875     *            The packagename can be found in the AndroidManifest.xml of
876     *            your application.
877     *            <p>
878     *            <code>&lt;manifest xmlns:android=&quot;...&quot;
879     *      package=&quot;<b>com.google.marvin.compass</b>&quot;&gt;</code>
880     *            </p>
881     *
882     * @param resourceId
883     *            Example: <code>R.raw.south_south_east</code>
884     *
885     * @return Code indicating success or failure. See {@link #ERROR} and {@link #SUCCESS}.
886     */
887    public int addSpeech(String text, String packagename, int resourceId) {
888        synchronized (mStartLock) {
889            mUtterances.put(text, makeResourceUri(packagename, resourceId));
890            return SUCCESS;
891        }
892    }
893
894    /**
895     * Adds a mapping between a CharSequence (may be spanned with TtsSpans) of text
896     * and a sound resource in a package. After a call to this method, subsequent calls to
897     * {@link #speak(String, int, HashMap)} will play the specified sound resource
898     * if it is available, or synthesize the text it is missing.
899     *
900     * @param text
901     *            The string of text. Example: <code>"south_south_east"</code>
902     *
903     * @param packagename
904     *            Pass the packagename of the application that contains the
905     *            resource. If the resource is in your own application (this is
906     *            the most common case), then put the packagename of your
907     *            application here.<br/>
908     *            Example: <b>"com.google.marvin.compass"</b><br/>
909     *            The packagename can be found in the AndroidManifest.xml of
910     *            your application.
911     *            <p>
912     *            <code>&lt;manifest xmlns:android=&quot;...&quot;
913     *      package=&quot;<b>com.google.marvin.compass</b>&quot;&gt;</code>
914     *            </p>
915     *
916     * @param resourceId
917     *            Example: <code>R.raw.south_south_east</code>
918     *
919     * @return Code indicating success or failure. See {@link #ERROR} and {@link #SUCCESS}.
920     */
921    public int addSpeech(CharSequence text, String packagename, int resourceId) {
922        synchronized (mStartLock) {
923            mUtterances.put(text, makeResourceUri(packagename, resourceId));
924            return SUCCESS;
925        }
926    }
927
928    /**
929     * Adds a mapping between a string of text and a sound file. Using this, it
930     * is possible to add custom pronounciations for a string of text.
931     * After a call to this method, subsequent calls to {@link #speak(String, int, HashMap)}
932     * will play the specified sound resource if it is available, or synthesize the text it is
933     * missing.
934     *
935     * @param text
936     *            The string of text. Example: <code>"south_south_east"</code>
937     * @param filename
938     *            The full path to the sound file (for example:
939     *            "/sdcard/mysounds/hello.wav")
940     *
941     * @return Code indicating success or failure. See {@link #ERROR} and {@link #SUCCESS}.
942     */
943    public int addSpeech(String text, String filename) {
944        synchronized (mStartLock) {
945            mUtterances.put(text, Uri.parse(filename));
946            return SUCCESS;
947        }
948    }
949
950    /**
951     * Adds a mapping between a CharSequence (may be spanned with TtsSpans and a sound file.
952     * Using this, it is possible to add custom pronounciations for a string of text.
953     * After a call to this method, subsequent calls to {@link #speak(String, int, HashMap)}
954     * will play the specified sound resource if it is available, or synthesize the text it is
955     * missing.
956     *
957     * @param text
958     *            The string of text. Example: <code>"south_south_east"</code>
959     * @param file
960     *            File object pointing to the sound file.
961     *
962     * @return Code indicating success or failure. See {@link #ERROR} and {@link #SUCCESS}.
963     */
964    public int addSpeech(CharSequence text, File file) {
965        synchronized (mStartLock) {
966            mUtterances.put(text, Uri.fromFile(file));
967            return SUCCESS;
968        }
969    }
970
971    /**
972     * Adds a mapping between a string of text and a sound resource in a
973     * package. Use this to add custom earcons.
974     *
975     * @see #playEarcon(String, int, HashMap)
976     *
977     * @param earcon The name of the earcon.
978     *            Example: <code>"[tick]"</code><br/>
979     *
980     * @param packagename
981     *            the package name of the application that contains the
982     *            resource. This can for instance be the package name of your own application.
983     *            Example: <b>"com.google.marvin.compass"</b><br/>
984     *            The package name can be found in the AndroidManifest.xml of
985     *            the application containing the resource.
986     *            <p>
987     *            <code>&lt;manifest xmlns:android=&quot;...&quot;
988     *      package=&quot;<b>com.google.marvin.compass</b>&quot;&gt;</code>
989     *            </p>
990     *
991     * @param resourceId
992     *            Example: <code>R.raw.tick_snd</code>
993     *
994     * @return Code indicating success or failure. See {@link #ERROR} and {@link #SUCCESS}.
995     */
996    public int addEarcon(String earcon, String packagename, int resourceId) {
997        synchronized(mStartLock) {
998            mEarcons.put(earcon, makeResourceUri(packagename, resourceId));
999            return SUCCESS;
1000        }
1001    }
1002
1003    /**
1004     * Adds a mapping between a string of text and a sound file.
1005     * Use this to add custom earcons.
1006     *
1007     * @see #playEarcon(String, int, HashMap)
1008     *
1009     * @param earcon
1010     *            The name of the earcon.
1011     *            Example: <code>"[tick]"</code>
1012     * @param filename
1013     *            The full path to the sound file (for example:
1014     *            "/sdcard/mysounds/tick.wav")
1015     *
1016     * @return Code indicating success or failure. See {@link #ERROR} and {@link #SUCCESS}.
1017     *
1018     * @deprecated As of API level 21, replaced by
1019     *         {@link #addEarcon(String, File)}.
1020     */
1021    @Deprecated
1022    public int addEarcon(String earcon, String filename) {
1023        synchronized(mStartLock) {
1024            mEarcons.put(earcon, Uri.parse(filename));
1025            return SUCCESS;
1026        }
1027    }
1028
1029    /**
1030     * Adds a mapping between a string of text and a sound file.
1031     * Use this to add custom earcons.
1032     *
1033     * @see #playEarcon(String, int, HashMap)
1034     *
1035     * @param earcon
1036     *            The name of the earcon.
1037     *            Example: <code>"[tick]"</code>
1038     * @param file
1039     *            File object pointing to the sound file.
1040     *
1041     * @return Code indicating success or failure. See {@link #ERROR} and {@link #SUCCESS}.
1042     */
1043    public int addEarcon(String earcon, File file) {
1044        synchronized(mStartLock) {
1045            mEarcons.put(earcon, Uri.fromFile(file));
1046            return SUCCESS;
1047        }
1048    }
1049
1050    private Uri makeResourceUri(String packageName, int resourceId) {
1051        return new Uri.Builder()
1052                .scheme(ContentResolver.SCHEME_ANDROID_RESOURCE)
1053                .encodedAuthority(packageName)
1054                .appendEncodedPath(String.valueOf(resourceId))
1055                .build();
1056    }
1057
1058    /**
1059     * Speaks the text using the specified queuing strategy and speech parameters, the text may
1060     * be spanned with TtsSpans.
1061     * This method is asynchronous, i.e. the method just adds the request to the queue of TTS
1062     * requests and then returns. The synthesis might not have finished (or even started!) at the
1063     * time when this method returns. In order to reliably detect errors during synthesis,
1064     * we recommend setting an utterance progress listener (see
1065     * {@link #setOnUtteranceProgressListener}) and using the
1066     * {@link Engine#KEY_PARAM_UTTERANCE_ID} parameter.
1067     *
1068     * @param text The string of text to be spoken. No longer than
1069     *            {@link #getMaxSpeechInputLength()} characters.
1070     * @param queueMode The queuing strategy to use, {@link #QUEUE_ADD} or {@link #QUEUE_FLUSH}.
1071     * @param params Parameters for the request. Can be null.
1072     *            Supported parameter names:
1073     *            {@link Engine#KEY_PARAM_STREAM},
1074     *            {@link Engine#KEY_PARAM_VOLUME},
1075     *            {@link Engine#KEY_PARAM_PAN}.
1076     *            Engine specific parameters may be passed in but the parameter keys
1077     *            must be prefixed by the name of the engine they are intended for. For example
1078     *            the keys "com.svox.pico_foo" and "com.svox.pico:bar" will be passed to the
1079     *            engine named "com.svox.pico" if it is being used.
1080     * @param utteranceId An unique identifier for this request.
1081     *
1082     * @return {@link #ERROR} or {@link #SUCCESS} of <b>queuing</b> the speak operation.
1083     */
1084    public int speak(final CharSequence text,
1085                     final int queueMode,
1086                     final Bundle params,
1087                     final String utteranceId) {
1088        return runAction(new Action<Integer>() {
1089            @Override
1090            public Integer run(ITextToSpeechService service) throws RemoteException {
1091                Uri utteranceUri = mUtterances.get(text);
1092                if (utteranceUri != null) {
1093                    return service.playAudio(getCallerIdentity(), utteranceUri, queueMode,
1094                            getParams(params), utteranceId);
1095                } else {
1096                    return service.speak(getCallerIdentity(), text, queueMode, getParams(params),
1097                            utteranceId);
1098                }
1099            }
1100        }, ERROR, "speak");
1101    }
1102
1103    /**
1104     * Speaks the string using the specified queuing strategy and speech parameters.
1105     * This method is asynchronous, i.e. the method just adds the request to the queue of TTS
1106     * requests and then returns. The synthesis might not have finished (or even started!) at the
1107     * time when this method returns. In order to reliably detect errors during synthesis,
1108     * we recommend setting an utterance progress listener (see
1109     * {@link #setOnUtteranceProgressListener}) and using the
1110     * {@link Engine#KEY_PARAM_UTTERANCE_ID} parameter.
1111     *
1112     * @param text The string of text to be spoken. No longer than
1113     *            {@link #getMaxSpeechInputLength()} characters.
1114     * @param queueMode The queuing strategy to use, {@link #QUEUE_ADD} or {@link #QUEUE_FLUSH}.
1115     * @param params Parameters for the request. Can be null.
1116     *            Supported parameter names:
1117     *            {@link Engine#KEY_PARAM_STREAM},
1118     *            {@link Engine#KEY_PARAM_UTTERANCE_ID},
1119     *            {@link Engine#KEY_PARAM_VOLUME},
1120     *            {@link Engine#KEY_PARAM_PAN}.
1121     *            Engine specific parameters may be passed in but the parameter keys
1122     *            must be prefixed by the name of the engine they are intended for. For example
1123     *            the keys "com.svox.pico_foo" and "com.svox.pico:bar" will be passed to the
1124     *            engine named "com.svox.pico" if it is being used.
1125     *
1126     * @return {@link #ERROR} or {@link #SUCCESS} of <b>queuing</b> the speak operation.
1127     * @deprecated As of API level 21, replaced by
1128     *         {@link #speak(CharSequence, int, Bundle, String)}.
1129     */
1130    @Deprecated
1131    public int speak(final String text, final int queueMode, final HashMap<String, String> params) {
1132        return speak(text, queueMode, convertParamsHashMaptoBundle(params),
1133                     params == null ? null : params.get(Engine.KEY_PARAM_UTTERANCE_ID));
1134    }
1135
1136    /**
1137     * Plays the earcon using the specified queueing mode and parameters.
1138     * The earcon must already have been added with {@link #addEarcon(String, String)} or
1139     * {@link #addEarcon(String, String, int)}.
1140     * This method is asynchronous, i.e. the method just adds the request to the queue of TTS
1141     * requests and then returns. The synthesis might not have finished (or even started!) at the
1142     * time when this method returns. In order to reliably detect errors during synthesis,
1143     * we recommend setting an utterance progress listener (see
1144     * {@link #setOnUtteranceProgressListener}) and using the
1145     * {@link Engine#KEY_PARAM_UTTERANCE_ID} parameter.
1146     *
1147     * @param earcon The earcon that should be played
1148     * @param queueMode {@link #QUEUE_ADD} or {@link #QUEUE_FLUSH}.
1149     * @param params Parameters for the request. Can be null.
1150     *            Supported parameter names:
1151     *            {@link Engine#KEY_PARAM_STREAM},
1152     *            Engine specific parameters may be passed in but the parameter keys
1153     *            must be prefixed by the name of the engine they are intended for. For example
1154     *            the keys "com.svox.pico_foo" and "com.svox.pico:bar" will be passed to the
1155     *            engine named "com.svox.pico" if it is being used.
1156     *
1157     * @return {@link #ERROR} or {@link #SUCCESS} of <b>queuing</b> the playEarcon operation.
1158     */
1159    public int playEarcon(final String earcon, final int queueMode,
1160            final Bundle params, final String utteranceId) {
1161        return runAction(new Action<Integer>() {
1162            @Override
1163            public Integer run(ITextToSpeechService service) throws RemoteException {
1164                Uri earconUri = mEarcons.get(earcon);
1165                if (earconUri == null) {
1166                    return ERROR;
1167                }
1168                return service.playAudio(getCallerIdentity(), earconUri, queueMode,
1169                        getParams(params), utteranceId);
1170            }
1171        }, ERROR, "playEarcon");
1172    }
1173
1174    /**
1175     * Plays the earcon using the specified queueing mode and parameters.
1176     * The earcon must already have been added with {@link #addEarcon(String, String)} or
1177     * {@link #addEarcon(String, String, int)}.
1178     * This method is asynchronous, i.e. the method just adds the request to the queue of TTS
1179     * requests and then returns. The synthesis might not have finished (or even started!) at the
1180     * time when this method returns. In order to reliably detect errors during synthesis,
1181     * we recommend setting an utterance progress listener (see
1182     * {@link #setOnUtteranceProgressListener}) and using the
1183     * {@link Engine#KEY_PARAM_UTTERANCE_ID} parameter.
1184     *
1185     * @param earcon The earcon that should be played
1186     * @param queueMode {@link #QUEUE_ADD} or {@link #QUEUE_FLUSH}.
1187     * @param params Parameters for the request. Can be null.
1188     *            Supported parameter names:
1189     *            {@link Engine#KEY_PARAM_STREAM},
1190     *            {@link Engine#KEY_PARAM_UTTERANCE_ID}.
1191     *            Engine specific parameters may be passed in but the parameter keys
1192     *            must be prefixed by the name of the engine they are intended for. For example
1193     *            the keys "com.svox.pico_foo" and "com.svox.pico:bar" will be passed to the
1194     *            engine named "com.svox.pico" if it is being used.
1195     *
1196     * @return {@link #ERROR} or {@link #SUCCESS} of <b>queuing</b> the playEarcon operation.
1197     * @deprecated As of API level 21, replaced by
1198     *         {@link #playEarcon(String, int, Bundle, String)}.
1199     */
1200    @Deprecated
1201    public int playEarcon(final String earcon, final int queueMode,
1202            final HashMap<String, String> params) {
1203        return playEarcon(earcon, queueMode, convertParamsHashMaptoBundle(params),
1204                          params == null ? null : params.get(Engine.KEY_PARAM_UTTERANCE_ID));
1205    }
1206
1207    /**
1208     * Plays silence for the specified amount of time using the specified
1209     * queue mode.
1210     * This method is asynchronous, i.e. the method just adds the request to the queue of TTS
1211     * requests and then returns. The synthesis might not have finished (or even started!) at the
1212     * time when this method returns. In order to reliably detect errors during synthesis,
1213     * we recommend setting an utterance progress listener (see
1214     * {@link #setOnUtteranceProgressListener}) and using the
1215     * {@link Engine#KEY_PARAM_UTTERANCE_ID} parameter.
1216     *
1217     * @param durationInMs The duration of the silence.
1218     * @param queueMode {@link #QUEUE_ADD} or {@link #QUEUE_FLUSH}.
1219     * @param utteranceId An unique identifier for this request.
1220     *
1221     * @return {@link #ERROR} or {@link #SUCCESS} of <b>queuing</b> the playSilentUtterance operation.
1222     */
1223    public int playSilentUtterance(final long durationInMs, final int queueMode,
1224            final String utteranceId) {
1225        return runAction(new Action<Integer>() {
1226            @Override
1227            public Integer run(ITextToSpeechService service) throws RemoteException {
1228                return service.playSilence(getCallerIdentity(), durationInMs,
1229                                           queueMode, utteranceId);
1230            }
1231        }, ERROR, "playSilentUtterance");
1232    }
1233
1234    /**
1235     * Plays silence for the specified amount of time using the specified
1236     * queue mode.
1237     * This method is asynchronous, i.e. the method just adds the request to the queue of TTS
1238     * requests and then returns. The synthesis might not have finished (or even started!) at the
1239     * time when this method returns. In order to reliably detect errors during synthesis,
1240     * we recommend setting an utterance progress listener (see
1241     * {@link #setOnUtteranceProgressListener}) and using the
1242     * {@link Engine#KEY_PARAM_UTTERANCE_ID} parameter.
1243     *
1244     * @param durationInMs The duration of the silence.
1245     * @param queueMode {@link #QUEUE_ADD} or {@link #QUEUE_FLUSH}.
1246     * @param params Parameters for the request. Can be null.
1247     *            Supported parameter names:
1248     *            {@link Engine#KEY_PARAM_UTTERANCE_ID}.
1249     *            Engine specific parameters may be passed in but the parameter keys
1250     *            must be prefixed by the name of the engine they are intended for. For example
1251     *            the keys "com.svox.pico_foo" and "com.svox.pico:bar" will be passed to the
1252     *            engine named "com.svox.pico" if it is being used.
1253     *
1254     * @return {@link #ERROR} or {@link #SUCCESS} of <b>queuing</b> the playSilence operation.
1255     * @deprecated As of API level 21, replaced by
1256     *         {@link #playSilentUtterance(long, int, String)}.
1257     */
1258    @Deprecated
1259    public int playSilence(final long durationInMs, final int queueMode,
1260            final HashMap<String, String> params) {
1261        return playSilentUtterance(durationInMs, queueMode,
1262                           params == null ? null : params.get(Engine.KEY_PARAM_UTTERANCE_ID));
1263    }
1264
1265    /**
1266     * Queries the engine for the set of features it supports for a given locale.
1267     * Features can either be framework defined, e.g.
1268     * {@link TextToSpeech.Engine#KEY_FEATURE_NETWORK_SYNTHESIS} or engine specific.
1269     * Engine specific keys must be prefixed by the name of the engine they
1270     * are intended for. These keys can be used as parameters to
1271     * {@link TextToSpeech#speak(String, int, java.util.HashMap)} and
1272     * {@link TextToSpeech#synthesizeToFile(String, java.util.HashMap, String)}.
1273     *
1274     * Features values are strings and their values must meet restrictions described in their
1275     * documentation.
1276     *
1277     * @param locale The locale to query features for.
1278     * @return Set instance. May return {@code null} on error.
1279     * @deprecated As of API level 21, please use voices. In order to query features of the voice,
1280     * call {@link #getVoices()} to retrieve the list of available voices and
1281     * {@link Voice#getFeatures()} to retrieve the set of features.
1282     */
1283    @Deprecated
1284    public Set<String> getFeatures(final Locale locale) {
1285        return runAction(new Action<Set<String>>() {
1286            @Override
1287            public Set<String> run(ITextToSpeechService service) throws RemoteException {
1288                String[] features = null;
1289                try {
1290                    features = service.getFeaturesForLanguage(
1291                        locale.getISO3Language(), locale.getISO3Country(), locale.getVariant());
1292                } catch(MissingResourceException e) {
1293                    Log.w(TAG, "Couldn't retrieve 3 letter ISO 639-2/T language and/or ISO 3166 " +
1294                            "country code for locale: " + locale, e);
1295                    return null;
1296                }
1297
1298                if (features != null) {
1299                    final Set<String> featureSet = new HashSet<String>();
1300                    Collections.addAll(featureSet, features);
1301                    return featureSet;
1302                }
1303                return null;
1304            }
1305        }, null, "getFeatures");
1306    }
1307
1308    /**
1309     * Checks whether the TTS engine is busy speaking. Note that a speech item is
1310     * considered complete once it's audio data has been sent to the audio mixer, or
1311     * written to a file. There might be a finite lag between this point, and when
1312     * the audio hardware completes playback.
1313     *
1314     * @return {@code true} if the TTS engine is speaking.
1315     */
1316    public boolean isSpeaking() {
1317        return runAction(new Action<Boolean>() {
1318            @Override
1319            public Boolean run(ITextToSpeechService service) throws RemoteException {
1320                return service.isSpeaking();
1321            }
1322        }, false, "isSpeaking");
1323    }
1324
1325    /**
1326     * Interrupts the current utterance (whether played or rendered to file) and discards other
1327     * utterances in the queue.
1328     *
1329     * @return {@link #ERROR} or {@link #SUCCESS}.
1330     */
1331    public int stop() {
1332        return runAction(new Action<Integer>() {
1333            @Override
1334            public Integer run(ITextToSpeechService service) throws RemoteException {
1335                return service.stop(getCallerIdentity());
1336            }
1337        }, ERROR, "stop");
1338    }
1339
1340    /**
1341     * Sets the speech rate.
1342     *
1343     * This has no effect on any pre-recorded speech.
1344     *
1345     * @param speechRate Speech rate. {@code 1.0} is the normal speech rate,
1346     *            lower values slow down the speech ({@code 0.5} is half the normal speech rate),
1347     *            greater values accelerate it ({@code 2.0} is twice the normal speech rate).
1348     *
1349     * @return {@link #ERROR} or {@link #SUCCESS}.
1350     */
1351    public int setSpeechRate(float speechRate) {
1352        if (speechRate > 0.0f) {
1353            int intRate = (int)(speechRate * 100);
1354            if (intRate > 0) {
1355                synchronized (mStartLock) {
1356                    mParams.putInt(Engine.KEY_PARAM_RATE, intRate);
1357                }
1358                return SUCCESS;
1359            }
1360        }
1361        return ERROR;
1362    }
1363
1364    /**
1365     * Sets the speech pitch for the TextToSpeech engine.
1366     *
1367     * This has no effect on any pre-recorded speech.
1368     *
1369     * @param pitch Speech pitch. {@code 1.0} is the normal pitch,
1370     *            lower values lower the tone of the synthesized voice,
1371     *            greater values increase it.
1372     *
1373     * @return {@link #ERROR} or {@link #SUCCESS}.
1374     */
1375    public int setPitch(float pitch) {
1376        if (pitch > 0.0f) {
1377            int intPitch = (int)(pitch * 100);
1378            if (intPitch > 0) {
1379                synchronized (mStartLock) {
1380                    mParams.putInt(Engine.KEY_PARAM_PITCH, intPitch);
1381                }
1382                return SUCCESS;
1383            }
1384        }
1385        return ERROR;
1386    }
1387
1388    /**
1389     * Sets the audio attributes to be used when speaking text or playing
1390     * back a file.
1391     *
1392     * @param audioAttributes Valid AudioAttributes instance.
1393     *
1394     * @return {@link #ERROR} or {@link #SUCCESS}.
1395     */
1396    public int setAudioAttributes(AudioAttributes audioAttributes) {
1397        if (audioAttributes != null) {
1398            synchronized (mStartLock) {
1399                mParams.putParcelable(Engine.KEY_PARAM_AUDIO_ATTRIBUTES,
1400                    audioAttributes);
1401            }
1402            return SUCCESS;
1403        }
1404        return ERROR;
1405    }
1406
1407    /**
1408     * @return the engine currently in use by this TextToSpeech instance.
1409     * @hide
1410     */
1411    public String getCurrentEngine() {
1412        return mCurrentEngine;
1413    }
1414
1415    /**
1416     * Returns a Locale instance describing the language currently being used as the default
1417     * Text-to-speech language.
1418     *
1419     * The locale object returned by this method is NOT a valid one. It has identical form to the
1420     * one in {@link #getLanguage()}. Please refer to {@link #getLanguage()} for more information.
1421     *
1422     * @return language, country (if any) and variant (if any) used by the client stored in a
1423     *     Locale instance, or {@code null} on error.
1424     * @deprecated As of API level 21, use <code>getDefaultVoice().getLocale()</code> ({@link
1425     *   #getDefaultVoice()})
1426     */
1427    @Deprecated
1428    public Locale getDefaultLanguage() {
1429        return runAction(new Action<Locale>() {
1430            @Override
1431            public Locale run(ITextToSpeechService service) throws RemoteException {
1432                String[] defaultLanguage = service.getClientDefaultLanguage();
1433
1434                return new Locale(defaultLanguage[0], defaultLanguage[1], defaultLanguage[2]);
1435            }
1436        }, null, "getDefaultLanguage");
1437    }
1438
1439    /**
1440     * Sets the text-to-speech language.
1441     * The TTS engine will try to use the closest match to the specified
1442     * language as represented by the Locale, but there is no guarantee that the exact same Locale
1443     * will be used. Use {@link #isLanguageAvailable(Locale)} to check the level of support
1444     * before choosing the language to use for the next utterances.
1445     *
1446     * This method sets the current voice to the default one for the given Locale;
1447     * {@link #getVoice()} can be used to retrieve it.
1448     *
1449     * @param loc The locale describing the language to be used.
1450     *
1451     * @return Code indicating the support status for the locale. See {@link #LANG_AVAILABLE},
1452     *         {@link #LANG_COUNTRY_AVAILABLE}, {@link #LANG_COUNTRY_VAR_AVAILABLE},
1453     *         {@link #LANG_MISSING_DATA} and {@link #LANG_NOT_SUPPORTED}.
1454     */
1455    public int setLanguage(final Locale loc) {
1456        return runAction(new Action<Integer>() {
1457            @Override
1458            public Integer run(ITextToSpeechService service) throws RemoteException {
1459                if (loc == null) {
1460                    return LANG_NOT_SUPPORTED;
1461                }
1462                String language = null, country = null;
1463                try {
1464                    language = loc.getISO3Language();
1465                } catch (MissingResourceException e) {
1466                    Log.w(TAG, "Couldn't retrieve ISO 639-2/T language code for locale: " + loc, e);
1467                    return LANG_NOT_SUPPORTED;
1468                }
1469
1470                try {
1471                    country = loc.getISO3Country();
1472                } catch (MissingResourceException e) {
1473                    Log.w(TAG, "Couldn't retrieve ISO 3166 country code for locale: " + loc, e);
1474                    return LANG_NOT_SUPPORTED;
1475                }
1476
1477                String variant = loc.getVariant();
1478
1479                // As of API level 21, setLanguage is implemented using setVoice.
1480                // (which, in the default implementation, will call loadLanguage on the service
1481                // interface).
1482
1483                // Sanitize locale using isLanguageAvailable.
1484                int result = service.isLanguageAvailable( language, country, variant);
1485                if (result >= LANG_AVAILABLE){
1486                    if (result < LANG_COUNTRY_VAR_AVAILABLE) {
1487                        variant = "";
1488                        if (result < LANG_COUNTRY_AVAILABLE) {
1489                            country = "";
1490                        }
1491                    }
1492                    // Get the default voice for the locale.
1493                    String voiceName = service.getDefaultVoiceNameFor(language, country, variant);
1494                    if (TextUtils.isEmpty(voiceName)) {
1495                        Log.w(TAG, "Couldn't find the default voice for " + language + "/" +
1496                                country + "/" + variant);
1497                        return LANG_NOT_SUPPORTED;
1498                    }
1499
1500                    // Load it.
1501                    if (service.loadVoice(getCallerIdentity(), voiceName) == TextToSpeech.ERROR) {
1502                        return LANG_NOT_SUPPORTED;
1503                    }
1504
1505                    mParams.putString(Engine.KEY_PARAM_VOICE_NAME, voiceName);
1506                    mParams.putString(Engine.KEY_PARAM_LANGUAGE, language);
1507                    mParams.putString(Engine.KEY_PARAM_COUNTRY, country);
1508                    mParams.putString(Engine.KEY_PARAM_VARIANT, variant);
1509                }
1510                return result;
1511            }
1512        }, LANG_NOT_SUPPORTED, "setLanguage");
1513    }
1514
1515    /**
1516     * Returns a Locale instance describing the language currently being used for synthesis
1517     * requests sent to the TextToSpeech engine.
1518     *
1519     * In Android 4.2 and before (API <= 17) this function returns the language that is currently
1520     * being used by the TTS engine. That is the last language set by this or any other
1521     * client by a {@link TextToSpeech#setLanguage} call to the same engine.
1522     *
1523     * In Android versions after 4.2 this function returns the language that is currently being
1524     * used for the synthesis requests sent from this client. That is the last language set
1525     * by a {@link TextToSpeech#setLanguage} call on this instance.
1526     *
1527     * If a voice is set (by {@link #setVoice(Voice)}), getLanguage will return the language of
1528     * the currently set voice.
1529     *
1530     * Please note that the Locale object returned by this method is NOT a valid Locale object. Its
1531     * language field contains a three-letter ISO 639-2/T code (where a proper Locale would use
1532     * a two-letter ISO 639-1 code), and the country field contains a three-letter ISO 3166 country
1533     * code (where a proper Locale would use a two-letter ISO 3166-1 code).
1534     *
1535     * @return language, country (if any) and variant (if any) used by the client stored in a
1536     *     Locale instance, or {@code null} on error.
1537     *
1538     * @deprecated As of API level 21, please use <code>getVoice().getLocale()</code>
1539     * ({@link #getVoice()}).
1540     */
1541    @Deprecated
1542    public Locale getLanguage() {
1543        return runAction(new Action<Locale>() {
1544            @Override
1545            public Locale run(ITextToSpeechService service) {
1546                /* No service call, but we're accessing mParams, hence need for
1547                   wrapping it as an Action instance */
1548                String lang = mParams.getString(Engine.KEY_PARAM_LANGUAGE, "");
1549                String country = mParams.getString(Engine.KEY_PARAM_COUNTRY, "");
1550                String variant = mParams.getString(Engine.KEY_PARAM_VARIANT, "");
1551                return new Locale(lang, country, variant);
1552            }
1553        }, null, "getLanguage");
1554    }
1555
1556    /**
1557     * Query the engine about the set of available languages.
1558     */
1559    public Set<Locale> getAvailableLanguages() {
1560        return runAction(new Action<Set<Locale>>() {
1561            @Override
1562            public Set<Locale> run(ITextToSpeechService service) throws RemoteException {
1563                List<Voice> voices = service.getVoices();
1564                if (voices == null) {
1565                    return new HashSet<Locale>();
1566                }
1567                HashSet<Locale> locales = new HashSet<Locale>();
1568                for (Voice voice : voices) {
1569                    locales.add(voice.getLocale());
1570                }
1571                return locales;
1572            }
1573        }, null, "getAvailableLanguages");
1574    }
1575
1576    /**
1577     * Query the engine about the set of available voices.
1578     *
1579     * Each TTS Engine can expose multiple voices for each locale, each with a different set of
1580     * features.
1581     *
1582     * @see #setVoice(Voice)
1583     * @see Voice
1584     */
1585    public Set<Voice> getVoices() {
1586        return runAction(new Action<Set<Voice>>() {
1587            @Override
1588            public Set<Voice> run(ITextToSpeechService service) throws RemoteException {
1589                List<Voice> voices = service.getVoices();
1590                return (voices != null)  ? new HashSet<Voice>(voices) : new HashSet<Voice>();
1591            }
1592        }, null, "getVoices");
1593    }
1594
1595    /**
1596     * Sets the text-to-speech voice.
1597     *
1598     * @param voice One of objects returned by {@link #getVoices()}.
1599     *
1600     * @return {@link #ERROR} or {@link #SUCCESS}.
1601     *
1602     * @see #getVoices
1603     * @see Voice
1604     */
1605    public int setVoice(final Voice voice) {
1606        return runAction(new Action<Integer>() {
1607            @Override
1608            public Integer run(ITextToSpeechService service) throws RemoteException {
1609                int result = service.loadVoice(getCallerIdentity(), voice.getName());
1610                if (result == SUCCESS) {
1611                    mParams.putString(Engine.KEY_PARAM_VOICE_NAME, voice.getName());
1612
1613                    // Set the language/country/variant, so #getLanguage will return the voice
1614                    // locale when called.
1615                    String language = "";
1616                    try {
1617                        language = voice.getLocale().getISO3Language();
1618                    } catch (MissingResourceException e) {
1619                        Log.w(TAG, "Couldn't retrieve ISO 639-2/T language code for locale: " +
1620                                voice.getLocale(), e);
1621                    }
1622
1623                    String country = "";
1624                    try {
1625                        country = voice.getLocale().getISO3Country();
1626                    } catch (MissingResourceException e) {
1627                        Log.w(TAG, "Couldn't retrieve ISO 3166 country code for locale: " +
1628                                voice.getLocale(), e);
1629                    }
1630                    mParams.putString(Engine.KEY_PARAM_LANGUAGE, language);
1631                    mParams.putString(Engine.KEY_PARAM_COUNTRY, country);
1632                    mParams.putString(Engine.KEY_PARAM_VARIANT, voice.getLocale().getVariant());
1633                }
1634                return result;
1635            }
1636        }, LANG_NOT_SUPPORTED, "setVoice");
1637    }
1638
1639    /**
1640     * Returns a Voice instance describing the voice currently being used for synthesis
1641     * requests sent to the TextToSpeech engine.
1642     *
1643     * @return Voice instance used by the client, or {@code null} if not set or on error.
1644     *
1645     * @see #getVoices
1646     * @see #setVoice
1647     * @see Voice
1648     */
1649    public Voice getVoice() {
1650        return runAction(new Action<Voice>() {
1651            @Override
1652            public Voice run(ITextToSpeechService service) throws RemoteException {
1653                String voiceName = mParams.getString(Engine.KEY_PARAM_VOICE_NAME, "");
1654                if (TextUtils.isEmpty(voiceName)) {
1655                    return null;
1656                }
1657                List<Voice> voices = service.getVoices();
1658                if (voices == null) {
1659                    return null;
1660                }
1661                for (Voice voice : voices) {
1662                    if (voice.getName().equals(voiceName)) {
1663                        return voice;
1664                    }
1665                }
1666                return null;
1667            }
1668        }, null, "getVoice");
1669    }
1670
1671    /**
1672     * Returns a Voice instance that's the default voice for the default Text-to-speech language.
1673     * @return The default voice instance for the default language, or {@code null} if not set or
1674     *     on error.
1675     */
1676    public Voice getDefaultVoice() {
1677        return runAction(new Action<Voice>() {
1678            @Override
1679            public Voice run(ITextToSpeechService service) throws RemoteException {
1680
1681                String[] defaultLanguage = service.getClientDefaultLanguage();
1682
1683                if (defaultLanguage == null || defaultLanguage.length == 0) {
1684                    Log.e(TAG, "service.getClientDefaultLanguage() returned empty array");
1685                    return null;
1686                }
1687                String language = defaultLanguage[0];
1688                String country = (defaultLanguage.length > 1) ? defaultLanguage[1] : "";
1689                String variant = (defaultLanguage.length > 2) ? defaultLanguage[2] : "";
1690
1691                // Sanitize the locale using isLanguageAvailable.
1692                int result = service.isLanguageAvailable(language, country, variant);
1693                if (result >= LANG_AVAILABLE){
1694                    if (result < LANG_COUNTRY_VAR_AVAILABLE) {
1695                        variant = "";
1696                        if (result < LANG_COUNTRY_AVAILABLE) {
1697                            country = "";
1698                        }
1699                    }
1700                } else {
1701                    // The default language is not supported.
1702                    return null;
1703                }
1704
1705                // Get the default voice name
1706                String voiceName = service.getDefaultVoiceNameFor(language, country, variant);
1707                if (TextUtils.isEmpty(voiceName)) {
1708                    return null;
1709                }
1710
1711                // Find it
1712                List<Voice> voices = service.getVoices();
1713                if (voices == null) {
1714                    return null;
1715                }
1716                for (Voice voice : voices) {
1717                    if (voice.getName().equals(voiceName)) {
1718                        return voice;
1719                    }
1720                }
1721                return null;
1722            }
1723        }, null, "getDefaultVoice");
1724    }
1725
1726
1727
1728    /**
1729     * Checks if the specified language as represented by the Locale is available and supported.
1730     *
1731     * @param loc The Locale describing the language to be used.
1732     *
1733     * @return Code indicating the support status for the locale. See {@link #LANG_AVAILABLE},
1734     *         {@link #LANG_COUNTRY_AVAILABLE}, {@link #LANG_COUNTRY_VAR_AVAILABLE},
1735     *         {@link #LANG_MISSING_DATA} and {@link #LANG_NOT_SUPPORTED}.
1736     */
1737    public int isLanguageAvailable(final Locale loc) {
1738        return runAction(new Action<Integer>() {
1739            @Override
1740            public Integer run(ITextToSpeechService service) throws RemoteException {
1741                String language = null, country = null;
1742
1743                try {
1744                    language = loc.getISO3Language();
1745                } catch (MissingResourceException e) {
1746                    Log.w(TAG, "Couldn't retrieve ISO 639-2/T language code for locale: " + loc, e);
1747                    return LANG_NOT_SUPPORTED;
1748                }
1749
1750                try {
1751                    country = loc.getISO3Country();
1752                } catch (MissingResourceException e) {
1753                    Log.w(TAG, "Couldn't retrieve ISO 3166 country code for locale: " + loc, e);
1754                    return LANG_NOT_SUPPORTED;
1755                }
1756
1757                return service.isLanguageAvailable(language, country, loc.getVariant());
1758            }
1759        }, LANG_NOT_SUPPORTED, "isLanguageAvailable");
1760    }
1761
1762    /**
1763     * Synthesizes the given text to a file using the specified parameters.
1764     * This method is asynchronous, i.e. the method just adds the request to the queue of TTS
1765     * requests and then returns. The synthesis might not have finished (or even started!) at the
1766     * time when this method returns. In order to reliably detect errors during synthesis,
1767     * we recommend setting an utterance progress listener (see
1768     * {@link #setOnUtteranceProgressListener}).
1769     *
1770     * @param text The text that should be synthesized. No longer than
1771     *            {@link #getMaxSpeechInputLength()} characters.
1772     * @param params Parameters for the request. Can be null.
1773     *            Engine specific parameters may be passed in but the parameter keys
1774     *            must be prefixed by the name of the engine they are intended for. For example
1775     *            the keys "com.svox.pico_foo" and "com.svox.pico:bar" will be passed to the
1776     *            engine named "com.svox.pico" if it is being used.
1777     * @param file File to write the generated audio data to.
1778     * @param utteranceId An unique identifier for this request.
1779     * @return {@link #ERROR} or {@link #SUCCESS} of <b>queuing</b> the synthesizeToFile operation.
1780     */
1781    public int synthesizeToFile(final CharSequence text, final Bundle params,
1782            final File file, final String utteranceId) {
1783        return runAction(new Action<Integer>() {
1784            @Override
1785            public Integer run(ITextToSpeechService service) throws RemoteException {
1786                ParcelFileDescriptor fileDescriptor;
1787                int returnValue;
1788                try {
1789                    if(file.exists() && !file.canWrite()) {
1790                        Log.e(TAG, "Can't write to " + file);
1791                        return ERROR;
1792                    }
1793                    fileDescriptor = ParcelFileDescriptor.open(file,
1794                            ParcelFileDescriptor.MODE_WRITE_ONLY |
1795                            ParcelFileDescriptor.MODE_CREATE |
1796                            ParcelFileDescriptor.MODE_TRUNCATE);
1797                    returnValue = service.synthesizeToFileDescriptor(getCallerIdentity(), text,
1798                            fileDescriptor, getParams(params), utteranceId);
1799                    fileDescriptor.close();
1800                    return returnValue;
1801                } catch (FileNotFoundException e) {
1802                    Log.e(TAG, "Opening file " + file + " failed", e);
1803                    return ERROR;
1804                } catch (IOException e) {
1805                    Log.e(TAG, "Closing file " + file + " failed", e);
1806                    return ERROR;
1807                }
1808            }
1809        }, ERROR, "synthesizeToFile");
1810    }
1811
1812    /**
1813     * Synthesizes the given text to a file using the specified parameters.
1814     * This method is asynchronous, i.e. the method just adds the request to the queue of TTS
1815     * requests and then returns. The synthesis might not have finished (or even started!) at the
1816     * time when this method returns. In order to reliably detect errors during synthesis,
1817     * we recommend setting an utterance progress listener (see
1818     * {@link #setOnUtteranceProgressListener}) and using the
1819     * {@link Engine#KEY_PARAM_UTTERANCE_ID} parameter.
1820     *
1821     * @param text The text that should be synthesized. No longer than
1822     *            {@link #getMaxSpeechInputLength()} characters.
1823     * @param params Parameters for the request. Can be null.
1824     *            Supported parameter names:
1825     *            {@link Engine#KEY_PARAM_UTTERANCE_ID}.
1826     *            Engine specific parameters may be passed in but the parameter keys
1827     *            must be prefixed by the name of the engine they are intended for. For example
1828     *            the keys "com.svox.pico_foo" and "com.svox.pico:bar" will be passed to the
1829     *            engine named "com.svox.pico" if it is being used.
1830     * @param filename Absolute file filename to write the generated audio data to.It should be
1831     *            something like "/sdcard/myappsounds/mysound.wav".
1832     *
1833     * @return {@link #ERROR} or {@link #SUCCESS} of <b>queuing</b> the synthesizeToFile operation.
1834     * @deprecated As of API level 21, replaced by
1835     *         {@link #synthesizeToFile(CharSequence, Bundle, File, String)}.
1836     */
1837    @Deprecated
1838    public int synthesizeToFile(final String text, final HashMap<String, String> params,
1839            final String filename) {
1840        return synthesizeToFile(text, convertParamsHashMaptoBundle(params),
1841                new File(filename), params.get(Engine.KEY_PARAM_UTTERANCE_ID));
1842    }
1843
1844    private Bundle convertParamsHashMaptoBundle(HashMap<String, String> params) {
1845        if (params != null && !params.isEmpty()) {
1846            Bundle bundle = new Bundle();
1847            copyIntParam(bundle, params, Engine.KEY_PARAM_STREAM);
1848            copyIntParam(bundle, params, Engine.KEY_PARAM_SESSION_ID);
1849            copyStringParam(bundle, params, Engine.KEY_PARAM_UTTERANCE_ID);
1850            copyFloatParam(bundle, params, Engine.KEY_PARAM_VOLUME);
1851            copyFloatParam(bundle, params, Engine.KEY_PARAM_PAN);
1852
1853            // Copy feature strings defined by the framework.
1854            copyStringParam(bundle, params, Engine.KEY_FEATURE_NETWORK_SYNTHESIS);
1855            copyStringParam(bundle, params, Engine.KEY_FEATURE_EMBEDDED_SYNTHESIS);
1856            copyIntParam(bundle, params, Engine.KEY_FEATURE_NETWORK_TIMEOUT_MS);
1857            copyIntParam(bundle, params, Engine.KEY_FEATURE_NETWORK_RETRIES_COUNT);
1858
1859            // Copy over all parameters that start with the name of the
1860            // engine that we are currently connected to. The engine is
1861            // free to interpret them as it chooses.
1862            if (!TextUtils.isEmpty(mCurrentEngine)) {
1863                for (Map.Entry<String, String> entry : params.entrySet()) {
1864                    final String key = entry.getKey();
1865                    if (key != null && key.startsWith(mCurrentEngine)) {
1866                        bundle.putString(key, entry.getValue());
1867                    }
1868                }
1869            }
1870
1871            return bundle;
1872        }
1873        return null;
1874    }
1875
1876    private Bundle getParams(Bundle params) {
1877        if (params != null && !params.isEmpty()) {
1878            Bundle bundle = new Bundle(mParams);
1879            bundle.putAll(params);
1880
1881            verifyIntegerBundleParam(bundle, Engine.KEY_PARAM_STREAM);
1882            verifyIntegerBundleParam(bundle, Engine.KEY_PARAM_SESSION_ID);
1883            verifyStringBundleParam(bundle, Engine.KEY_PARAM_UTTERANCE_ID);
1884            verifyFloatBundleParam(bundle, Engine.KEY_PARAM_VOLUME);
1885            verifyFloatBundleParam(bundle, Engine.KEY_PARAM_PAN);
1886
1887            // Copy feature strings defined by the framework.
1888            verifyBooleanBundleParam(bundle, Engine.KEY_FEATURE_NETWORK_SYNTHESIS);
1889            verifyBooleanBundleParam(bundle, Engine.KEY_FEATURE_EMBEDDED_SYNTHESIS);
1890            verifyIntegerBundleParam(bundle, Engine.KEY_FEATURE_NETWORK_TIMEOUT_MS);
1891            verifyIntegerBundleParam(bundle, Engine.KEY_FEATURE_NETWORK_RETRIES_COUNT);
1892
1893            return bundle;
1894        } else {
1895            return mParams;
1896        }
1897    }
1898
1899    private static boolean verifyIntegerBundleParam(Bundle bundle, String key) {
1900        if (bundle.containsKey(key)) {
1901            if (!(bundle.get(key) instanceof Integer ||
1902                    bundle.get(key) instanceof Long)) {
1903                bundle.remove(key);
1904                Log.w(TAG, "Synthesis request paramter " + key + " containst value "
1905                        + " with invalid type. Should be an Integer or a Long");
1906                return false;
1907            }
1908        }
1909        return true;
1910    }
1911
1912    private static boolean verifyStringBundleParam(Bundle bundle, String key) {
1913        if (bundle.containsKey(key)) {
1914            if (!(bundle.get(key) instanceof String)) {
1915                bundle.remove(key);
1916                Log.w(TAG, "Synthesis request paramter " + key + " containst value "
1917                        + " with invalid type. Should be a String");
1918                return false;
1919            }
1920        }
1921        return true;
1922    }
1923
1924    private static boolean verifyBooleanBundleParam(Bundle bundle, String key) {
1925        if (bundle.containsKey(key)) {
1926            if (!(bundle.get(key) instanceof Boolean ||
1927                    bundle.get(key) instanceof String)) {
1928                bundle.remove(key);
1929                Log.w(TAG, "Synthesis request paramter " + key + " containst value "
1930                        + " with invalid type. Should be a Boolean or String");
1931                return false;
1932            }
1933        }
1934        return true;
1935    }
1936
1937
1938    private static boolean verifyFloatBundleParam(Bundle bundle, String key) {
1939        if (bundle.containsKey(key)) {
1940            if (!(bundle.get(key) instanceof Float ||
1941                    bundle.get(key) instanceof Double)) {
1942                bundle.remove(key);
1943                Log.w(TAG, "Synthesis request paramter " + key + " containst value "
1944                        + " with invalid type. Should be a Float or a Double");
1945                return false;
1946            }
1947        }
1948        return true;
1949    }
1950
1951    private void copyStringParam(Bundle bundle, HashMap<String, String> params, String key) {
1952        String value = params.get(key);
1953        if (value != null) {
1954            bundle.putString(key, value);
1955        }
1956    }
1957
1958    private void copyIntParam(Bundle bundle, HashMap<String, String> params, String key) {
1959        String valueString = params.get(key);
1960        if (!TextUtils.isEmpty(valueString)) {
1961            try {
1962                int value = Integer.parseInt(valueString);
1963                bundle.putInt(key, value);
1964            } catch (NumberFormatException ex) {
1965                // don't set the value in the bundle
1966            }
1967        }
1968    }
1969
1970    private void copyFloatParam(Bundle bundle, HashMap<String, String> params, String key) {
1971        String valueString = params.get(key);
1972        if (!TextUtils.isEmpty(valueString)) {
1973            try {
1974                float value = Float.parseFloat(valueString);
1975                bundle.putFloat(key, value);
1976            } catch (NumberFormatException ex) {
1977                // don't set the value in the bundle
1978            }
1979        }
1980    }
1981
1982    /**
1983     * Sets the listener that will be notified when synthesis of an utterance completes.
1984     *
1985     * @param listener The listener to use.
1986     *
1987     * @return {@link #ERROR} or {@link #SUCCESS}.
1988     *
1989     * @deprecated Use {@link #setOnUtteranceProgressListener(UtteranceProgressListener)}
1990     *        instead.
1991     */
1992    @Deprecated
1993    public int setOnUtteranceCompletedListener(final OnUtteranceCompletedListener listener) {
1994        mUtteranceProgressListener = UtteranceProgressListener.from(listener);
1995        return TextToSpeech.SUCCESS;
1996    }
1997
1998    /**
1999     * Sets the listener that will be notified of various events related to the
2000     * synthesis of a given utterance.
2001     *
2002     * See {@link UtteranceProgressListener} and
2003     * {@link TextToSpeech.Engine#KEY_PARAM_UTTERANCE_ID}.
2004     *
2005     * @param listener the listener to use.
2006     * @return {@link #ERROR} or {@link #SUCCESS}
2007     */
2008    public int setOnUtteranceProgressListener(UtteranceProgressListener listener) {
2009        mUtteranceProgressListener = listener;
2010        return TextToSpeech.SUCCESS;
2011    }
2012
2013    /**
2014     * Sets the TTS engine to use.
2015     *
2016     * @deprecated This doesn't inform callers when the TTS engine has been
2017     *        initialized. {@link #TextToSpeech(Context, OnInitListener, String)}
2018     *        can be used with the appropriate engine name. Also, there is no
2019     *        guarantee that the engine specified will be loaded. If it isn't
2020     *        installed or disabled, the user / system wide defaults will apply.
2021     *
2022     * @param enginePackageName The package name for the synthesis engine (e.g. "com.svox.pico")
2023     *
2024     * @return {@link #ERROR} or {@link #SUCCESS}.
2025     */
2026    @Deprecated
2027    public int setEngineByPackageName(String enginePackageName) {
2028        mRequestedEngine = enginePackageName;
2029        return initTts();
2030    }
2031
2032    /**
2033     * Gets the package name of the default speech synthesis engine.
2034     *
2035     * @return Package name of the TTS engine that the user has chosen
2036     *        as their default.
2037     */
2038    public String getDefaultEngine() {
2039        return mEnginesHelper.getDefaultEngine();
2040    }
2041
2042    /**
2043     * Checks whether the user's settings should override settings requested
2044     * by the calling application. As of the Ice cream sandwich release,
2045     * user settings never forcibly override the app's settings.
2046     */
2047    @Deprecated
2048    public boolean areDefaultsEnforced() {
2049        return false;
2050    }
2051
2052    /**
2053     * Gets a list of all installed TTS engines.
2054     *
2055     * @return A list of engine info objects. The list can be empty, but never {@code null}.
2056     */
2057    public List<EngineInfo> getEngines() {
2058        return mEnginesHelper.getEngines();
2059    }
2060
2061    private class Connection implements ServiceConnection {
2062        private ITextToSpeechService mService;
2063
2064        private SetupConnectionAsyncTask mOnSetupConnectionAsyncTask;
2065
2066        private boolean mEstablished;
2067
2068        private final ITextToSpeechCallback.Stub mCallback = new ITextToSpeechCallback.Stub() {
2069            public void onStop(String utteranceId, boolean isStarted) throws RemoteException {
2070                UtteranceProgressListener listener = mUtteranceProgressListener;
2071                if (listener != null) {
2072                    listener.onStop(utteranceId, isStarted);
2073                }
2074            };
2075
2076            @Override
2077            public void onSuccess(String utteranceId) {
2078                UtteranceProgressListener listener = mUtteranceProgressListener;
2079                if (listener != null) {
2080                    listener.onDone(utteranceId);
2081                }
2082            }
2083
2084            @Override
2085            public void onError(String utteranceId, int errorCode) {
2086                UtteranceProgressListener listener = mUtteranceProgressListener;
2087                if (listener != null) {
2088                    listener.onError(utteranceId);
2089                }
2090            }
2091
2092            @Override
2093            public void onStart(String utteranceId) {
2094                UtteranceProgressListener listener = mUtteranceProgressListener;
2095                if (listener != null) {
2096                    listener.onStart(utteranceId);
2097                }
2098            }
2099        };
2100
2101        private class SetupConnectionAsyncTask extends AsyncTask<Void, Void, Integer> {
2102            private final ComponentName mName;
2103
2104            public SetupConnectionAsyncTask(ComponentName name) {
2105                mName = name;
2106            }
2107
2108            @Override
2109            protected Integer doInBackground(Void... params) {
2110                synchronized(mStartLock) {
2111                    if (isCancelled()) {
2112                        return null;
2113                    }
2114
2115                    try {
2116                        mService.setCallback(getCallerIdentity(), mCallback);
2117
2118                        if (mParams.getString(Engine.KEY_PARAM_LANGUAGE) == null) {
2119                            String[] defaultLanguage = mService.getClientDefaultLanguage();
2120                            mParams.putString(Engine.KEY_PARAM_LANGUAGE, defaultLanguage[0]);
2121                            mParams.putString(Engine.KEY_PARAM_COUNTRY, defaultLanguage[1]);
2122                            mParams.putString(Engine.KEY_PARAM_VARIANT, defaultLanguage[2]);
2123
2124                            // Get the default voice for the locale.
2125                            String defaultVoiceName = mService.getDefaultVoiceNameFor(
2126                                defaultLanguage[0], defaultLanguage[1], defaultLanguage[2]);
2127                            mParams.putString(Engine.KEY_PARAM_VOICE_NAME, defaultVoiceName);
2128                        }
2129
2130                        Log.i(TAG, "Set up connection to " + mName);
2131                        return SUCCESS;
2132                    } catch (RemoteException re) {
2133                        Log.e(TAG, "Error connecting to service, setCallback() failed");
2134                        return ERROR;
2135                    }
2136                }
2137            }
2138
2139            @Override
2140            protected void onPostExecute(Integer result) {
2141                synchronized(mStartLock) {
2142                    if (mOnSetupConnectionAsyncTask == this) {
2143                        mOnSetupConnectionAsyncTask = null;
2144                    }
2145                    mEstablished = true;
2146                    dispatchOnInit(result);
2147                }
2148            }
2149        }
2150
2151        @Override
2152        public void onServiceConnected(ComponentName name, IBinder service) {
2153            synchronized(mStartLock) {
2154                mConnectingServiceConnection = null;
2155
2156                Log.i(TAG, "Connected to " + name);
2157
2158                if (mOnSetupConnectionAsyncTask != null) {
2159                    mOnSetupConnectionAsyncTask.cancel(false);
2160                }
2161
2162                mService = ITextToSpeechService.Stub.asInterface(service);
2163                mServiceConnection = Connection.this;
2164
2165                mEstablished = false;
2166                mOnSetupConnectionAsyncTask = new SetupConnectionAsyncTask(name);
2167                mOnSetupConnectionAsyncTask.execute();
2168            }
2169        }
2170
2171        public IBinder getCallerIdentity() {
2172            return mCallback;
2173        }
2174
2175        /**
2176         * Clear connection related fields and cancel mOnServiceConnectedAsyncTask if set.
2177         *
2178         * @return true if we cancel mOnSetupConnectionAsyncTask in progress.
2179         */
2180        private boolean clearServiceConnection() {
2181            synchronized(mStartLock) {
2182                boolean result = false;
2183                if (mOnSetupConnectionAsyncTask != null) {
2184                    result = mOnSetupConnectionAsyncTask.cancel(false);
2185                    mOnSetupConnectionAsyncTask = null;
2186                }
2187
2188                mService = null;
2189                // If this is the active connection, clear it
2190                if (mServiceConnection == this) {
2191                    mServiceConnection = null;
2192                }
2193                return result;
2194            }
2195        }
2196
2197        @Override
2198        public void onServiceDisconnected(ComponentName name) {
2199            Log.i(TAG, "Asked to disconnect from " + name);
2200            if (clearServiceConnection()) {
2201                /* We need to protect against a rare case where engine
2202                 * dies just after successful connection - and we process onServiceDisconnected
2203                 * before OnServiceConnectedAsyncTask.onPostExecute. onServiceDisconnected cancels
2204                 * OnServiceConnectedAsyncTask.onPostExecute and we don't call dispatchOnInit
2205                 * with ERROR as argument.
2206                 */
2207                dispatchOnInit(ERROR);
2208            }
2209        }
2210
2211        public void disconnect() {
2212            mContext.unbindService(this);
2213            clearServiceConnection();
2214        }
2215
2216        public boolean isEstablished() {
2217            return mService != null && mEstablished;
2218        }
2219
2220        public <R> R runAction(Action<R> action, R errorResult, String method,
2221                boolean reconnect, boolean onlyEstablishedConnection) {
2222            synchronized (mStartLock) {
2223                try {
2224                    if (mService == null) {
2225                        Log.w(TAG, method + " failed: not connected to TTS engine");
2226                        return errorResult;
2227                    }
2228                    if (onlyEstablishedConnection && !isEstablished()) {
2229                        Log.w(TAG, method + " failed: TTS engine connection not fully set up");
2230                        return errorResult;
2231                    }
2232                    return action.run(mService);
2233                } catch (RemoteException ex) {
2234                    Log.e(TAG, method + " failed", ex);
2235                    if (reconnect) {
2236                        disconnect();
2237                        initTts();
2238                    }
2239                    return errorResult;
2240                }
2241            }
2242        }
2243    }
2244
2245    private interface Action<R> {
2246        R run(ITextToSpeechService service) throws RemoteException;
2247    }
2248
2249    /**
2250     * Information about an installed text-to-speech engine.
2251     *
2252     * @see TextToSpeech#getEngines
2253     */
2254    public static class EngineInfo {
2255        /**
2256         * Engine package name..
2257         */
2258        public String name;
2259        /**
2260         * Localized label for the engine.
2261         */
2262        public String label;
2263        /**
2264         * Icon for the engine.
2265         */
2266        public int icon;
2267        /**
2268         * Whether this engine is a part of the system
2269         * image.
2270         *
2271         * @hide
2272         */
2273        public boolean system;
2274        /**
2275         * The priority the engine declares for the the intent filter
2276         * {@code android.intent.action.TTS_SERVICE}
2277         *
2278         * @hide
2279         */
2280        public int priority;
2281
2282        @Override
2283        public String toString() {
2284            return "EngineInfo{name=" + name + "}";
2285        }
2286
2287    }
2288
2289    /**
2290     * Limit of length of input string passed to speak and synthesizeToFile.
2291     *
2292     * @see #speak
2293     * @see #synthesizeToFile
2294     */
2295    public static int getMaxSpeechInputLength() {
2296        return 4000;
2297    }
2298}
2299