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