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