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