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