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