TextToSpeech.java revision 8a3d9f03199b93e07a5f5edc14660eab6cd233e7
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.SdkConstant;
19import android.annotation.SdkConstant.SdkConstantType;
20import android.content.ComponentName;
21import android.content.ContentResolver;
22import android.content.Context;
23import android.content.Intent;
24import android.content.ServiceConnection;
25import android.media.AudioManager;
26import android.net.Uri;
27import android.os.AsyncTask;
28import android.os.Bundle;
29import android.os.IBinder;
30import android.os.RemoteException;
31import android.provider.Settings;
32import android.text.TextUtils;
33import android.util.Log;
34
35import java.util.Collections;
36import java.util.HashMap;
37import java.util.HashSet;
38import java.util.List;
39import java.util.Locale;
40import java.util.Map;
41import java.util.Set;
42
43/**
44 *
45 * Synthesizes speech from text for immediate playback or to create a sound file.
46 * <p>A TextToSpeech instance can only be used to synthesize text once it has completed its
47 * initialization. Implement the {@link TextToSpeech.OnInitListener} to be
48 * notified of the completion of the initialization.<br>
49 * When you are done using the TextToSpeech instance, call the {@link #shutdown()} method
50 * to release the native resources used by the TextToSpeech engine.
51 *
52 */
53public class TextToSpeech {
54
55    private static final String TAG = "TextToSpeech";
56
57    /**
58     * Denotes a successful operation.
59     */
60    public static final int SUCCESS = 0;
61    /**
62     * Denotes a generic operation failure.
63     */
64    public static final int ERROR = -1;
65
66    /**
67     * Queue mode where all entries in the playback queue (media to be played
68     * and text to be synthesized) are dropped and replaced by the new entry.
69     * Queues are flushed with respect to a given calling app. Entries in the queue
70     * from other callees are not discarded.
71     */
72    public static final int QUEUE_FLUSH = 0;
73    /**
74     * Queue mode where the new entry is added at the end of the playback queue.
75     */
76    public static final int QUEUE_ADD = 1;
77    /**
78     * Queue mode where the entire playback queue is purged. This is different
79     * from {@link #QUEUE_FLUSH} in that all entries are purged, not just entries
80     * from a given caller.
81     *
82     * @hide
83     */
84    static final int QUEUE_DESTROY = 2;
85
86    /**
87     * Denotes the language is available exactly as specified by the locale.
88     */
89    public static final int LANG_COUNTRY_VAR_AVAILABLE = 2;
90
91    /**
92     * Denotes the language is available for the language and country specified
93     * by the locale, but not the variant.
94     */
95    public static final int LANG_COUNTRY_AVAILABLE = 1;
96
97    /**
98     * Denotes the language is available for the language by the locale,
99     * but not the country and variant.
100     */
101    public static final int LANG_AVAILABLE = 0;
102
103    /**
104     * Denotes the language data is missing.
105     */
106    public static final int LANG_MISSING_DATA = -1;
107
108    /**
109     * Denotes the language is not supported.
110     */
111    public static final int LANG_NOT_SUPPORTED = -2;
112
113    /**
114     * Broadcast Action: The TextToSpeech synthesizer has completed processing
115     * of all the text in the speech queue.
116     *
117     * Note that this notifies callers when the <b>engine</b> has finished has
118     * processing text data. Audio playback might not have completed (or even started)
119     * at this point. If you wish to be notified when this happens, see
120     * {@link OnUtteranceCompletedListener}.
121     */
122    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
123    public static final String ACTION_TTS_QUEUE_PROCESSING_COMPLETED =
124            "android.speech.tts.TTS_QUEUE_PROCESSING_COMPLETED";
125
126    /**
127     * Interface definition of a callback to be invoked indicating the completion of the
128     * TextToSpeech engine initialization.
129     */
130    public interface OnInitListener {
131        /**
132         * Called to signal the completion of the TextToSpeech engine initialization.
133         *
134         * @param status {@link TextToSpeech#SUCCESS} or {@link TextToSpeech#ERROR}.
135         */
136        public void onInit(int status);
137    }
138
139    /**
140     * Listener that will be called when the TTS service has
141     * completed synthesizing an utterance. This is only called if the utterance
142     * has an utterance ID (see {@link TextToSpeech.Engine#KEY_PARAM_UTTERANCE_ID}).
143     *
144     * @deprecated Use {@link UtteranceProgressListener} instead.
145     */
146    @Deprecated
147    public interface OnUtteranceCompletedListener {
148        /**
149         * Called when an utterance has been synthesized.
150         *
151         * @param utteranceId the identifier of the utterance.
152         */
153        public void onUtteranceCompleted(String utteranceId);
154    }
155
156    /**
157     * Constants and parameter names for controlling text-to-speech. These include:
158     *
159     * <ul>
160     *     <li>
161     *         Intents to ask engine to install data or check its data and
162     *         extras for a TTS engine's check data activity.
163     *     </li>
164     *     <li>
165     *         Keys for the parameters passed with speak commands, e.g.
166     *         {@link Engine#KEY_PARAM_UTTERANCE_ID}, {@link Engine#KEY_PARAM_STREAM}.
167     *     </li>
168     *     <li>
169     *         A list of feature strings that engines might support, e.g
170     *         {@link Engine#KEY_FEATURE_NETWORK_SYNTHESIS}). These values may be passed in to
171     *         {@link TextToSpeech#speak} and {@link TextToSpeech#synthesizeToFile} to modify
172     *         engine behaviour. The engine can be queried for the set of features it supports
173     *         through {@link TextToSpeech#getFeatures(java.util.Locale)}.
174     *     </li>
175     * </ul>
176     */
177    public class Engine {
178
179        /**
180         * Default speech rate.
181         * @hide
182         */
183        public static final int DEFAULT_RATE = 100;
184
185        /**
186         * Default pitch.
187         * @hide
188         */
189        public static final int DEFAULT_PITCH = 100;
190
191        /**
192         * Default volume.
193         * @hide
194         */
195        public static final float DEFAULT_VOLUME = 1.0f;
196
197        /**
198         * Default pan (centered).
199         * @hide
200         */
201        public static final float DEFAULT_PAN = 0.0f;
202
203        /**
204         * Default value for {@link Settings.Secure#TTS_USE_DEFAULTS}.
205         * @hide
206         */
207        public static final int USE_DEFAULTS = 0; // false
208
209        /**
210         * Package name of the default TTS engine.
211         *
212         * @hide
213         * @deprecated No longer in use, the default engine is determined by
214         *         the sort order defined in {@link TtsEngines}. Note that
215         *         this doesn't "break" anything because there is no guarantee that
216         *         the engine specified below is installed on a given build, let
217         *         alone be the default.
218         */
219        @Deprecated
220        public static final String DEFAULT_ENGINE = "com.svox.pico";
221
222        /**
223         * Default audio stream used when playing synthesized speech.
224         */
225        public static final int DEFAULT_STREAM = AudioManager.STREAM_MUSIC;
226
227        /**
228         * Indicates success when checking the installation status of the resources used by the
229         * TextToSpeech engine with the {@link #ACTION_CHECK_TTS_DATA} intent.
230         */
231        public static final int CHECK_VOICE_DATA_PASS = 1;
232
233        /**
234         * Indicates failure when checking the installation status of the resources used by the
235         * TextToSpeech engine with the {@link #ACTION_CHECK_TTS_DATA} intent.
236         */
237        public static final int CHECK_VOICE_DATA_FAIL = 0;
238
239        /**
240         * Indicates erroneous data when checking the installation status of the resources used by
241         * the TextToSpeech engine with the {@link #ACTION_CHECK_TTS_DATA} intent.
242         *
243         * @deprecated Use CHECK_VOICE_DATA_FAIL instead.
244         */
245        @Deprecated
246        public static final int CHECK_VOICE_DATA_BAD_DATA = -1;
247
248        /**
249         * Indicates missing resources when checking the installation status of the resources used
250         * by the TextToSpeech engine with the {@link #ACTION_CHECK_TTS_DATA} intent.
251         *
252         * @deprecated Use CHECK_VOICE_DATA_FAIL instead.
253         */
254        @Deprecated
255        public static final int CHECK_VOICE_DATA_MISSING_DATA = -2;
256
257        /**
258         * Indicates missing storage volume when checking the installation status of the resources
259         * used by the TextToSpeech engine with the {@link #ACTION_CHECK_TTS_DATA} intent.
260         *
261         * @deprecated Use CHECK_VOICE_DATA_FAIL instead.
262         */
263        @Deprecated
264        public static final int CHECK_VOICE_DATA_MISSING_VOLUME = -3;
265
266        /**
267         * Intent for starting a TTS service. Services that handle this intent must
268         * extend {@link TextToSpeechService}. Normal applications should not use this intent
269         * directly, instead they should talk to the TTS service using the the methods in this
270         * class.
271         */
272        @SdkConstant(SdkConstantType.SERVICE_ACTION)
273        public static final String INTENT_ACTION_TTS_SERVICE =
274                "android.intent.action.TTS_SERVICE";
275
276        /**
277         * Name under which a text to speech engine publishes information about itself.
278         * This meta-data should reference an XML resource containing a
279         * <code>&lt;{@link android.R.styleable#TextToSpeechEngine tts-engine}&gt;</code>
280         * tag.
281         */
282        public static final String SERVICE_META_DATA = "android.speech.tts";
283
284        // intents to ask engine to install data or check its data
285        /**
286         * Activity Action: Triggers the platform TextToSpeech engine to
287         * start the activity that installs the resource files on the device
288         * that are required for TTS to be operational. Since the installation
289         * of the data can be interrupted or declined by the user, the application
290         * shouldn't expect successful installation upon return from that intent,
291         * and if need be, should check installation status with
292         * {@link #ACTION_CHECK_TTS_DATA}.
293         */
294        @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
295        public static final String ACTION_INSTALL_TTS_DATA =
296                "android.speech.tts.engine.INSTALL_TTS_DATA";
297
298        /**
299         * Broadcast Action: broadcast to signal the change in the list of available
300         * languages or/and their features.
301         */
302        @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
303        public static final String ACTION_TTS_DATA_INSTALLED =
304                "android.speech.tts.engine.TTS_DATA_INSTALLED";
305
306        /**
307         * Activity Action: Starts the activity from the platform TextToSpeech
308         * engine to verify the proper installation and availability of the
309         * resource files on the system. Upon completion, the activity will
310         * return one of the following codes:
311         * {@link #CHECK_VOICE_DATA_PASS},
312         * {@link #CHECK_VOICE_DATA_FAIL},
313         * <p> Moreover, the data received in the activity result will contain the following
314         * fields:
315         * <ul>
316         *   <li>{@link #EXTRA_AVAILABLE_VOICES} which contains an ArrayList<String> of all the
317         *   available voices. The format of each voice is: lang-COUNTRY-variant where COUNTRY and
318         *   variant are optional (ie, "eng" or "eng-USA" or "eng-USA-FEMALE").</li>
319         *   <li>{@link #EXTRA_UNAVAILABLE_VOICES} which contains an ArrayList<String> of all the
320         *   unavailable voices (ones that user can install). The format of each voice is:
321         *   lang-COUNTRY-variant where COUNTRY and variant are optional (ie, "eng" or
322         *   "eng-USA" or "eng-USA-FEMALE").</li>
323         * </ul>
324         */
325        @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
326        public static final String ACTION_CHECK_TTS_DATA =
327                "android.speech.tts.engine.CHECK_TTS_DATA";
328
329        /**
330         * Activity intent for getting some sample text to use for demonstrating TTS. Specific
331         * locale have to be requested by passing following extra parameters:
332         * <ul>
333         *   <li>language</li>
334         *   <li>country</li>
335         *   <li>variant</li>
336         * </ul>
337         *
338         * Upon completion, the activity result may contain the following fields:
339         * <ul>
340         *   <li>{@link #EXTRA_SAMPLE_TEXT} which contains an String with sample text.</li>
341         * </ul>
342         */
343        @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
344        public static final String ACTION_GET_SAMPLE_TEXT =
345                "android.speech.tts.engine.GET_SAMPLE_TEXT";
346
347        /**
348         * Extra information received with the {@link #ACTION_GET_SAMPLE_TEXT} intent result where
349         * the TextToSpeech engine returns an String with sample text for requested voice
350         */
351        public static final String EXTRA_SAMPLE_TEXT = "sampleText";
352
353
354        // extras for a TTS engine's check data activity
355        /**
356         * Extra information received with the {@link #ACTION_CHECK_TTS_DATA} intent result where
357         * the TextToSpeech engine returns an ArrayList<String> of all the available voices.
358         * The format of each voice is: lang-COUNTRY-variant where COUNTRY and variant are
359         * optional (ie, "eng" or "eng-USA" or "eng-USA-FEMALE").
360         */
361        public static final String EXTRA_AVAILABLE_VOICES = "availableVoices";
362
363        /**
364         * Extra information received with the {@link #ACTION_CHECK_TTS_DATA} intent result where
365         * the TextToSpeech engine returns an ArrayList<String> of all the unavailable voices.
366         * The format of each voice is: lang-COUNTRY-variant where COUNTRY and variant are
367         * optional (ie, "eng" or "eng-USA" or "eng-USA-FEMALE").
368         */
369        public static final String EXTRA_UNAVAILABLE_VOICES = "unavailableVoices";
370
371        /**
372         * Extra information received with the {@link #ACTION_CHECK_TTS_DATA} intent result where
373         * the TextToSpeech engine specifies the path to its resources.
374         *
375         * It may be used by language packages to find out where to put their data.
376         *
377         * @deprecated TTS engine implementation detail, this information has no use for
378         * text-to-speech API client.
379         */
380        @Deprecated
381        public static final String EXTRA_VOICE_DATA_ROOT_DIRECTORY = "dataRoot";
382
383        /**
384         * Extra information received with the {@link #ACTION_CHECK_TTS_DATA} intent result where
385         * the TextToSpeech engine specifies the file names of its resources under the
386         * resource path.
387         *
388         * @deprecated TTS engine implementation detail, this information has no use for
389         * text-to-speech API client.
390         */
391        @Deprecated
392        public static final String EXTRA_VOICE_DATA_FILES = "dataFiles";
393
394        /**
395         * Extra information received with the {@link #ACTION_CHECK_TTS_DATA} intent result where
396         * the TextToSpeech engine specifies the locale associated with each resource file.
397         *
398         * @deprecated TTS engine implementation detail, this information has no use for
399         * text-to-speech API client.
400         */
401        @Deprecated
402        public static final String EXTRA_VOICE_DATA_FILES_INFO = "dataFilesInfo";
403
404        /**
405         * Extra information sent with the {@link #ACTION_CHECK_TTS_DATA} intent where the
406         * caller indicates to the TextToSpeech engine which specific sets of voice data to
407         * check for by sending an ArrayList<String> of the voices that are of interest.
408         * The format of each voice is: lang-COUNTRY-variant where COUNTRY and variant are
409         * optional (ie, "eng" or "eng-USA" or "eng-USA-FEMALE").
410         *
411         * @deprecated Redundant functionality, checking for existence of specific sets of voice
412         * data can be done on client side.
413         */
414        @Deprecated
415        public static final String EXTRA_CHECK_VOICE_DATA_FOR = "checkVoiceDataFor";
416
417        // extras for a TTS engine's data installation
418        /**
419         * Extra information received with the {@link #ACTION_TTS_DATA_INSTALLED} intent result.
420         * It indicates whether the data files for the synthesis engine were successfully
421         * installed. The installation was initiated with the  {@link #ACTION_INSTALL_TTS_DATA}
422         * intent. The possible values for this extra are
423         * {@link TextToSpeech#SUCCESS} and {@link TextToSpeech#ERROR}.
424         *
425         * @deprecated No longer in use. If client ise interested in information about what
426         * changed, is should send ACTION_CHECK_TTS_DATA intent to discover available voices.
427         */
428        @Deprecated
429        public static final String EXTRA_TTS_DATA_INSTALLED = "dataInstalled";
430
431        // keys for the parameters passed with speak commands. Hidden keys are used internally
432        // to maintain engine state for each TextToSpeech instance.
433        /**
434         * @hide
435         */
436        public static final String KEY_PARAM_RATE = "rate";
437
438        /**
439         * @hide
440         */
441        public static final String KEY_PARAM_LANGUAGE = "language";
442
443        /**
444         * @hide
445         */
446        public static final String KEY_PARAM_COUNTRY = "country";
447
448        /**
449         * @hide
450         */
451        public static final String KEY_PARAM_VARIANT = "variant";
452
453        /**
454         * @hide
455         */
456        public static final String KEY_PARAM_ENGINE = "engine";
457
458        /**
459         * @hide
460         */
461        public static final String KEY_PARAM_PITCH = "pitch";
462
463        /**
464         * Parameter key to specify the audio stream type to be used when speaking text
465         * or playing back a file. The value should be one of the STREAM_ constants
466         * defined in {@link AudioManager}.
467         *
468         * @see TextToSpeech#speak(String, int, HashMap)
469         * @see TextToSpeech#playEarcon(String, int, HashMap)
470         */
471        public static final String KEY_PARAM_STREAM = "streamType";
472
473        /**
474         * Parameter key to identify an utterance in the
475         * {@link TextToSpeech.OnUtteranceCompletedListener} after text has been
476         * spoken, a file has been played back or a silence duration has elapsed.
477         *
478         * @see TextToSpeech#speak(String, int, HashMap)
479         * @see TextToSpeech#playEarcon(String, int, HashMap)
480         * @see TextToSpeech#synthesizeToFile(String, HashMap, String)
481         */
482        public static final String KEY_PARAM_UTTERANCE_ID = "utteranceId";
483
484        /**
485         * Parameter key to specify the speech volume relative to the current stream type
486         * volume used when speaking text. Volume is specified as a float ranging from 0 to 1
487         * where 0 is silence, and 1 is the maximum volume (the default behavior).
488         *
489         * @see TextToSpeech#speak(String, int, HashMap)
490         * @see TextToSpeech#playEarcon(String, int, HashMap)
491         */
492        public static final String KEY_PARAM_VOLUME = "volume";
493
494        /**
495         * Parameter key to specify how the speech is panned from left to right when speaking text.
496         * Pan is specified as a float ranging from -1 to +1 where -1 maps to a hard-left pan,
497         * 0 to center (the default behavior), and +1 to hard-right.
498         *
499         * @see TextToSpeech#speak(String, int, HashMap)
500         * @see TextToSpeech#playEarcon(String, int, HashMap)
501         */
502        public static final String KEY_PARAM_PAN = "pan";
503
504        /**
505         * Feature key for network synthesis. See {@link TextToSpeech#getFeatures(Locale)}
506         * for a description of how feature keys work. If set (and supported by the engine
507         * as per {@link TextToSpeech#getFeatures(Locale)}, the engine must
508         * use network based synthesis.
509         *
510         * @see TextToSpeech#speak(String, int, java.util.HashMap)
511         * @see TextToSpeech#synthesizeToFile(String, java.util.HashMap, String)
512         * @see TextToSpeech#getFeatures(java.util.Locale)
513         */
514        public static final String KEY_FEATURE_NETWORK_SYNTHESIS = "networkTts";
515
516        /**
517         * Feature key for embedded synthesis. See {@link TextToSpeech#getFeatures(Locale)}
518         * for a description of how feature keys work. If set and supported by the engine
519         * as per {@link TextToSpeech#getFeatures(Locale)}, the engine must synthesize
520         * text on-device (without making network requests).
521         *
522         * @see TextToSpeech#speak(String, int, java.util.HashMap)
523         * @see TextToSpeech#synthesizeToFile(String, java.util.HashMap, String)
524         * @see TextToSpeech#getFeatures(java.util.Locale)
525         */
526        public static final String KEY_FEATURE_EMBEDDED_SYNTHESIS = "embeddedTts";
527    }
528
529    private final Context mContext;
530    private Connection mServiceConnection;
531    private OnInitListener mInitListener;
532    // Written from an unspecified application thread, read from
533    // a binder thread.
534    private volatile UtteranceProgressListener mUtteranceProgressListener;
535    private final Object mStartLock = new Object();
536
537    private String mRequestedEngine;
538    // Whether to initialize this TTS object with the default engine,
539    // if the requested engine is not available. Valid only if mRequestedEngine
540    // is not null. Used only for testing, though potentially useful API wise
541    // too.
542    private final boolean mUseFallback;
543    private final Map<String, Uri> mEarcons;
544    private final Map<String, Uri> mUtterances;
545    private final Bundle mParams = new Bundle();
546    private final TtsEngines mEnginesHelper;
547    private final String mPackageName;
548    private volatile String mCurrentEngine = null;
549
550    /**
551     * The constructor for the TextToSpeech class, using the default TTS engine.
552     * This will also initialize the associated TextToSpeech engine if it isn't already running.
553     *
554     * @param context
555     *            The context this instance is running in.
556     * @param listener
557     *            The {@link TextToSpeech.OnInitListener} that will be called when the
558     *            TextToSpeech engine has initialized.
559     */
560    public TextToSpeech(Context context, OnInitListener listener) {
561        this(context, listener, null);
562    }
563
564    /**
565     * The constructor for the TextToSpeech class, using the given TTS engine.
566     * This will also initialize the associated TextToSpeech engine if it isn't already running.
567     *
568     * @param context
569     *            The context this instance is running in.
570     * @param listener
571     *            The {@link TextToSpeech.OnInitListener} that will be called when the
572     *            TextToSpeech engine has initialized.
573     * @param engine Package name of the TTS engine to use.
574     */
575    public TextToSpeech(Context context, OnInitListener listener, String engine) {
576        this(context, listener, engine, null, true);
577    }
578
579    /**
580     * Used by the framework to instantiate TextToSpeech objects with a supplied
581     * package name, instead of using {@link android.content.Context#getPackageName()}
582     *
583     * @hide
584     */
585    public TextToSpeech(Context context, OnInitListener listener, String engine,
586            String packageName, boolean useFallback) {
587        mContext = context;
588        mInitListener = listener;
589        mRequestedEngine = engine;
590        mUseFallback = useFallback;
591
592        mEarcons = new HashMap<String, Uri>();
593        mUtterances = new HashMap<String, Uri>();
594        mUtteranceProgressListener = null;
595
596        mEnginesHelper = new TtsEngines(mContext);
597        if (packageName != null) {
598            mPackageName = packageName;
599        } else {
600            mPackageName = mContext.getPackageName();
601        }
602        initTts();
603    }
604
605    private <R> R runActionNoReconnect(Action<R> action, R errorResult, String method) {
606        return runAction(action, errorResult, method, false);
607    }
608
609    private <R> R runAction(Action<R> action, R errorResult, String method) {
610        return runAction(action, errorResult, method, true);
611    }
612
613    private <R> R runAction(Action<R> action, R errorResult, String method, boolean reconnect) {
614        synchronized (mStartLock) {
615            if (mServiceConnection == null) {
616                Log.w(TAG, method + " failed: not bound to TTS engine");
617                return errorResult;
618            }
619            return mServiceConnection.runAction(action, errorResult, method, reconnect);
620        }
621    }
622
623    private int initTts() {
624        // Step 1: Try connecting to the engine that was requested.
625        if (mRequestedEngine != null) {
626            if (mEnginesHelper.isEngineInstalled(mRequestedEngine)) {
627                if (connectToEngine(mRequestedEngine)) {
628                    mCurrentEngine = mRequestedEngine;
629                    return SUCCESS;
630                } else if (!mUseFallback) {
631                    mCurrentEngine = null;
632                    dispatchOnInit(ERROR);
633                    return ERROR;
634                }
635            } else if (!mUseFallback) {
636                Log.i(TAG, "Requested engine not installed: " + mRequestedEngine);
637                mCurrentEngine = null;
638                dispatchOnInit(ERROR);
639                return ERROR;
640            }
641        }
642
643        // Step 2: Try connecting to the user's default engine.
644        final String defaultEngine = getDefaultEngine();
645        if (defaultEngine != null && !defaultEngine.equals(mRequestedEngine)) {
646            if (connectToEngine(defaultEngine)) {
647                mCurrentEngine = defaultEngine;
648                return SUCCESS;
649            }
650        }
651
652        // Step 3: Try connecting to the highest ranked engine in the
653        // system.
654        final String highestRanked = mEnginesHelper.getHighestRankedEngineName();
655        if (highestRanked != null && !highestRanked.equals(mRequestedEngine) &&
656                !highestRanked.equals(defaultEngine)) {
657            if (connectToEngine(highestRanked)) {
658                mCurrentEngine = highestRanked;
659                return SUCCESS;
660            }
661        }
662
663        // NOTE: The API currently does not allow the caller to query whether
664        // they are actually connected to any engine. This might fail for various
665        // reasons like if the user disables all her TTS engines.
666
667        mCurrentEngine = null;
668        dispatchOnInit(ERROR);
669        return ERROR;
670    }
671
672    private boolean connectToEngine(String engine) {
673        Connection connection = new Connection();
674        Intent intent = new Intent(Engine.INTENT_ACTION_TTS_SERVICE);
675        intent.setPackage(engine);
676        boolean bound = mContext.bindService(intent, connection, Context.BIND_AUTO_CREATE);
677        if (!bound) {
678            Log.e(TAG, "Failed to bind to " + engine);
679            return false;
680        } else {
681            Log.i(TAG, "Sucessfully bound to " + engine);
682            return true;
683        }
684    }
685
686    private void dispatchOnInit(int result) {
687        synchronized (mStartLock) {
688            if (mInitListener != null) {
689                mInitListener.onInit(result);
690                mInitListener = null;
691            }
692        }
693    }
694
695    private IBinder getCallerIdentity() {
696        return mServiceConnection.getCallerIdentity();
697    }
698
699    /**
700     * Releases the resources used by the TextToSpeech engine.
701     * It is good practice for instance to call this method in the onDestroy() method of an Activity
702     * so the TextToSpeech engine can be cleanly stopped.
703     */
704    public void shutdown() {
705        runActionNoReconnect(new Action<Void>() {
706            @Override
707            public Void run(ITextToSpeechService service) throws RemoteException {
708                service.setCallback(getCallerIdentity(), null);
709                service.stop(getCallerIdentity());
710                mServiceConnection.disconnect();
711                // Context#unbindService does not result in a call to
712                // ServiceConnection#onServiceDisconnected. As a result, the
713                // service ends up being destroyed (if there are no other open
714                // connections to it) but the process lives on and the
715                // ServiceConnection continues to refer to the destroyed service.
716                //
717                // This leads to tons of log spam about SynthThread being dead.
718                mServiceConnection = null;
719                mCurrentEngine = null;
720                return null;
721            }
722        }, null, "shutdown");
723    }
724
725    /**
726     * Adds a mapping between a string of text and a sound resource in a
727     * package. After a call to this method, subsequent calls to
728     * {@link #speak(String, int, HashMap)} will play the specified sound resource
729     * if it is available, or synthesize the text it is missing.
730     *
731     * @param text
732     *            The string of text. Example: <code>"south_south_east"</code>
733     *
734     * @param packagename
735     *            Pass the packagename of the application that contains the
736     *            resource. If the resource is in your own application (this is
737     *            the most common case), then put the packagename of your
738     *            application here.<br/>
739     *            Example: <b>"com.google.marvin.compass"</b><br/>
740     *            The packagename can be found in the AndroidManifest.xml of
741     *            your application.
742     *            <p>
743     *            <code>&lt;manifest xmlns:android=&quot;...&quot;
744     *      package=&quot;<b>com.google.marvin.compass</b>&quot;&gt;</code>
745     *            </p>
746     *
747     * @param resourceId
748     *            Example: <code>R.raw.south_south_east</code>
749     *
750     * @return Code indicating success or failure. See {@link #ERROR} and {@link #SUCCESS}.
751     */
752    public int addSpeech(String text, String packagename, int resourceId) {
753        synchronized (mStartLock) {
754            mUtterances.put(text, makeResourceUri(packagename, resourceId));
755            return SUCCESS;
756        }
757    }
758
759    /**
760     * Adds a mapping between a string of text and a sound file. Using this, it
761     * is possible to add custom pronounciations for a string of text.
762     * After a call to this method, subsequent calls to {@link #speak(String, int, HashMap)}
763     * will play the specified sound resource if it is available, or synthesize the text it is
764     * missing.
765     *
766     * @param text
767     *            The string of text. Example: <code>"south_south_east"</code>
768     * @param filename
769     *            The full path to the sound file (for example:
770     *            "/sdcard/mysounds/hello.wav")
771     *
772     * @return Code indicating success or failure. See {@link #ERROR} and {@link #SUCCESS}.
773     */
774    public int addSpeech(String text, String filename) {
775        synchronized (mStartLock) {
776            mUtterances.put(text, Uri.parse(filename));
777            return SUCCESS;
778        }
779    }
780
781
782    /**
783     * Adds a mapping between a string of text and a sound resource in a
784     * package. Use this to add custom earcons.
785     *
786     * @see #playEarcon(String, int, HashMap)
787     *
788     * @param earcon The name of the earcon.
789     *            Example: <code>"[tick]"</code><br/>
790     *
791     * @param packagename
792     *            the package name of the application that contains the
793     *            resource. This can for instance be the package name of your own application.
794     *            Example: <b>"com.google.marvin.compass"</b><br/>
795     *            The package name can be found in the AndroidManifest.xml of
796     *            the application containing the resource.
797     *            <p>
798     *            <code>&lt;manifest xmlns:android=&quot;...&quot;
799     *      package=&quot;<b>com.google.marvin.compass</b>&quot;&gt;</code>
800     *            </p>
801     *
802     * @param resourceId
803     *            Example: <code>R.raw.tick_snd</code>
804     *
805     * @return Code indicating success or failure. See {@link #ERROR} and {@link #SUCCESS}.
806     */
807    public int addEarcon(String earcon, String packagename, int resourceId) {
808        synchronized(mStartLock) {
809            mEarcons.put(earcon, makeResourceUri(packagename, resourceId));
810            return SUCCESS;
811        }
812    }
813
814    /**
815     * Adds a mapping between a string of text and a sound file.
816     * Use this to add custom earcons.
817     *
818     * @see #playEarcon(String, int, HashMap)
819     *
820     * @param earcon
821     *            The name of the earcon.
822     *            Example: <code>"[tick]"</code>
823     * @param filename
824     *            The full path to the sound file (for example:
825     *            "/sdcard/mysounds/tick.wav")
826     *
827     * @return Code indicating success or failure. See {@link #ERROR} and {@link #SUCCESS}.
828     */
829    public int addEarcon(String earcon, String filename) {
830        synchronized(mStartLock) {
831            mEarcons.put(earcon, Uri.parse(filename));
832            return SUCCESS;
833        }
834    }
835
836    private Uri makeResourceUri(String packageName, int resourceId) {
837        return new Uri.Builder()
838                .scheme(ContentResolver.SCHEME_ANDROID_RESOURCE)
839                .encodedAuthority(packageName)
840                .appendEncodedPath(String.valueOf(resourceId))
841                .build();
842    }
843
844    /**
845     * Speaks the string using the specified queuing strategy and speech parameters.
846     * This method is asynchronous, i.e. the method just adds the request to the queue of TTS
847     * requests and then returns. The synthesis might not have finished (or even started!) at the
848     * time when this method returns. In order to reliably detect errors during synthesis,
849     * we recommend setting an utterance progress listener (see
850     * {@link #setOnUtteranceProgressListener}) and using the
851     * {@link Engine#KEY_PARAM_UTTERANCE_ID} parameter.
852     *
853     * @param text The string of text to be spoken. No longer than
854     *            {@link #getMaxSpeechInputLength()} characters.
855     * @param queueMode The queuing strategy to use, {@link #QUEUE_ADD} or {@link #QUEUE_FLUSH}.
856     * @param params Parameters for the request. Can be null.
857     *            Supported parameter names:
858     *            {@link Engine#KEY_PARAM_STREAM},
859     *            {@link Engine#KEY_PARAM_UTTERANCE_ID},
860     *            {@link Engine#KEY_PARAM_VOLUME},
861     *            {@link Engine#KEY_PARAM_PAN}.
862     *            Engine specific parameters may be passed in but the parameter keys
863     *            must be prefixed by the name of the engine they are intended for. For example
864     *            the keys "com.svox.pico_foo" and "com.svox.pico:bar" will be passed to the
865     *            engine named "com.svox.pico" if it is being used.
866     *
867     * @return {@link #ERROR} or {@link #SUCCESS} of <b>queuing</b> the speak operation.
868     */
869    public int speak(final String text, final int queueMode, final HashMap<String, String> params) {
870        return runAction(new Action<Integer>() {
871            @Override
872            public Integer run(ITextToSpeechService service) throws RemoteException {
873                Uri utteranceUri = mUtterances.get(text);
874                if (utteranceUri != null) {
875                    return service.playAudio(getCallerIdentity(), utteranceUri, queueMode,
876                            getParams(params));
877                } else {
878                    return service.speak(getCallerIdentity(), text, queueMode, getParams(params));
879                }
880            }
881        }, ERROR, "speak");
882    }
883
884    /**
885     * Plays the earcon using the specified queueing mode and parameters.
886     * The earcon must already have been added with {@link #addEarcon(String, String)} or
887     * {@link #addEarcon(String, String, int)}.
888     * This method is asynchronous, i.e. the method just adds the request to the queue of TTS
889     * requests and then returns. The synthesis might not have finished (or even started!) at the
890     * time when this method returns. In order to reliably detect errors during synthesis,
891     * we recommend setting an utterance progress listener (see
892     * {@link #setOnUtteranceProgressListener}) and using the
893     * {@link Engine#KEY_PARAM_UTTERANCE_ID} parameter.
894     *
895     * @param earcon The earcon that should be played
896     * @param queueMode {@link #QUEUE_ADD} or {@link #QUEUE_FLUSH}.
897     * @param params Parameters for the request. Can be null.
898     *            Supported parameter names:
899     *            {@link Engine#KEY_PARAM_STREAM},
900     *            {@link Engine#KEY_PARAM_UTTERANCE_ID}.
901     *            Engine specific parameters may be passed in but the parameter keys
902     *            must be prefixed by the name of the engine they are intended for. For example
903     *            the keys "com.svox.pico_foo" and "com.svox.pico:bar" will be passed to the
904     *            engine named "com.svox.pico" if it is being used.
905     *
906     * @return {@link #ERROR} or {@link #SUCCESS} of <b>queuing</b> the playEarcon operation.
907     */
908    public int playEarcon(final String earcon, final int queueMode,
909            final HashMap<String, String> params) {
910        return runAction(new Action<Integer>() {
911            @Override
912            public Integer run(ITextToSpeechService service) throws RemoteException {
913                Uri earconUri = mEarcons.get(earcon);
914                if (earconUri == null) {
915                    return ERROR;
916                }
917                return service.playAudio(getCallerIdentity(), earconUri, queueMode,
918                        getParams(params));
919            }
920        }, ERROR, "playEarcon");
921    }
922
923    /**
924     * Plays silence for the specified amount of time using the specified
925     * queue mode.
926     * This method is asynchronous, i.e. the method just adds the request to the queue of TTS
927     * requests and then returns. The synthesis might not have finished (or even started!) at the
928     * time when this method returns. In order to reliably detect errors during synthesis,
929     * we recommend setting an utterance progress listener (see
930     * {@link #setOnUtteranceProgressListener}) and using the
931     * {@link Engine#KEY_PARAM_UTTERANCE_ID} parameter.
932     *
933     * @param durationInMs The duration of the silence.
934     * @param queueMode {@link #QUEUE_ADD} or {@link #QUEUE_FLUSH}.
935     * @param params Parameters for the request. Can be null.
936     *            Supported parameter names:
937     *            {@link Engine#KEY_PARAM_UTTERANCE_ID}.
938     *            Engine specific parameters may be passed in but the parameter keys
939     *            must be prefixed by the name of the engine they are intended for. For example
940     *            the keys "com.svox.pico_foo" and "com.svox.pico:bar" will be passed to the
941     *            engine named "com.svox.pico" if it is being used.
942     *
943     * @return {@link #ERROR} or {@link #SUCCESS} of <b>queuing</b> the playSilence operation.
944     */
945    public int playSilence(final long durationInMs, final int queueMode,
946            final HashMap<String, String> params) {
947        return runAction(new Action<Integer>() {
948            @Override
949            public Integer run(ITextToSpeechService service) throws RemoteException {
950                return service.playSilence(getCallerIdentity(), durationInMs, queueMode,
951                        getParams(params));
952            }
953        }, ERROR, "playSilence");
954    }
955
956    /**
957     * Queries the engine for the set of features it supports for a given locale.
958     * Features can either be framework defined, e.g.
959     * {@link TextToSpeech.Engine#KEY_FEATURE_NETWORK_SYNTHESIS} or engine specific.
960     * Engine specific keys must be prefixed by the name of the engine they
961     * are intended for. These keys can be used as parameters to
962     * {@link TextToSpeech#speak(String, int, java.util.HashMap)} and
963     * {@link TextToSpeech#synthesizeToFile(String, java.util.HashMap, String)}.
964     *
965     * Features are boolean flags, and their values in the synthesis parameters
966     * must be behave as per {@link Boolean#parseBoolean(String)}.
967     *
968     * @param locale The locale to query features for.
969     */
970    public Set<String> getFeatures(final Locale locale) {
971        return runAction(new Action<Set<String>>() {
972            @Override
973            public Set<String> run(ITextToSpeechService service) throws RemoteException {
974                String[] features = service.getFeaturesForLanguage(
975                        locale.getISO3Language(), locale.getISO3Country(), locale.getVariant());
976                if (features != null) {
977                    final Set<String> featureSet = new HashSet<String>();
978                    Collections.addAll(featureSet, features);
979                    return featureSet;
980                }
981                return null;
982            }
983        }, null, "getFeatures");
984    }
985
986    /**
987     * Checks whether the TTS engine is busy speaking. Note that a speech item is
988     * considered complete once it's audio data has been sent to the audio mixer, or
989     * written to a file. There might be a finite lag between this point, and when
990     * the audio hardware completes playback.
991     *
992     * @return {@code true} if the TTS engine is speaking.
993     */
994    public boolean isSpeaking() {
995        return runAction(new Action<Boolean>() {
996            @Override
997            public Boolean run(ITextToSpeechService service) throws RemoteException {
998                return service.isSpeaking();
999            }
1000        }, false, "isSpeaking");
1001    }
1002
1003    /**
1004     * Interrupts the current utterance (whether played or rendered to file) and discards other
1005     * utterances in the queue.
1006     *
1007     * @return {@link #ERROR} or {@link #SUCCESS}.
1008     */
1009    public int stop() {
1010        return runAction(new Action<Integer>() {
1011            @Override
1012            public Integer run(ITextToSpeechService service) throws RemoteException {
1013                return service.stop(getCallerIdentity());
1014            }
1015        }, ERROR, "stop");
1016    }
1017
1018    /**
1019     * Sets the speech rate.
1020     *
1021     * This has no effect on any pre-recorded speech.
1022     *
1023     * @param speechRate Speech rate. {@code 1.0} is the normal speech rate,
1024     *            lower values slow down the speech ({@code 0.5} is half the normal speech rate),
1025     *            greater values accelerate it ({@code 2.0} is twice the normal speech rate).
1026     *
1027     * @return {@link #ERROR} or {@link #SUCCESS}.
1028     */
1029    public int setSpeechRate(float speechRate) {
1030        if (speechRate > 0.0f) {
1031            int intRate = (int)(speechRate * 100);
1032            if (intRate > 0) {
1033                synchronized (mStartLock) {
1034                    mParams.putInt(Engine.KEY_PARAM_RATE, intRate);
1035                }
1036                return SUCCESS;
1037            }
1038        }
1039        return ERROR;
1040    }
1041
1042    /**
1043     * Sets the speech pitch for the TextToSpeech engine.
1044     *
1045     * This has no effect on any pre-recorded speech.
1046     *
1047     * @param pitch Speech pitch. {@code 1.0} is the normal pitch,
1048     *            lower values lower the tone of the synthesized voice,
1049     *            greater values increase it.
1050     *
1051     * @return {@link #ERROR} or {@link #SUCCESS}.
1052     */
1053    public int setPitch(float pitch) {
1054        if (pitch > 0.0f) {
1055            int intPitch = (int)(pitch * 100);
1056            if (intPitch > 0) {
1057                synchronized (mStartLock) {
1058                    mParams.putInt(Engine.KEY_PARAM_PITCH, intPitch);
1059                }
1060                return SUCCESS;
1061            }
1062        }
1063        return ERROR;
1064    }
1065
1066    /**
1067     * @return the engine currently in use by this TextToSpeech instance.
1068     * @hide
1069     */
1070    public String getCurrentEngine() {
1071        return mCurrentEngine;
1072    }
1073
1074    /**
1075     * Returns a Locale instance describing the language currently being used as the default
1076     * Text-to-speech language.
1077     *
1078     * @return language, country (if any) and variant (if any) used by the client stored in a
1079     *     Locale instance, or {@code null} on error.
1080     */
1081    public Locale getDefaultLanguage() {
1082        return runAction(new Action<Locale>() {
1083            @Override
1084            public Locale run(ITextToSpeechService service) throws RemoteException {
1085                String[] defaultLanguage = service.getClientDefaultLanguage();
1086
1087                return new Locale(defaultLanguage[0], defaultLanguage[1], defaultLanguage[2]);
1088            }
1089        }, null, "getDefaultLanguage");
1090    }
1091
1092    /**
1093     * Sets the text-to-speech language.
1094     * The TTS engine will try to use the closest match to the specified
1095     * language as represented by the Locale, but there is no guarantee that the exact same Locale
1096     * will be used. Use {@link #isLanguageAvailable(Locale)} to check the level of support
1097     * before choosing the language to use for the next utterances.
1098     *
1099     * @param loc The locale describing the language to be used.
1100     *
1101     * @return Code indicating the support status for the locale. See {@link #LANG_AVAILABLE},
1102     *         {@link #LANG_COUNTRY_AVAILABLE}, {@link #LANG_COUNTRY_VAR_AVAILABLE},
1103     *         {@link #LANG_MISSING_DATA} and {@link #LANG_NOT_SUPPORTED}.
1104     */
1105    public int setLanguage(final Locale loc) {
1106        return runAction(new Action<Integer>() {
1107            @Override
1108            public Integer run(ITextToSpeechService service) throws RemoteException {
1109                if (loc == null) {
1110                    return LANG_NOT_SUPPORTED;
1111                }
1112                String language = loc.getISO3Language();
1113                String country = loc.getISO3Country();
1114                String variant = loc.getVariant();
1115                // Check if the language, country, variant are available, and cache
1116                // the available parts.
1117                // Note that the language is not actually set here, instead it is cached so it
1118                // will be associated with all upcoming utterances.
1119
1120                int result = service.loadLanguage(getCallerIdentity(), language, country, variant);
1121                if (result >= LANG_AVAILABLE){
1122                    if (result < LANG_COUNTRY_VAR_AVAILABLE) {
1123                        variant = "";
1124                        if (result < LANG_COUNTRY_AVAILABLE) {
1125                            country = "";
1126                        }
1127                    }
1128                    mParams.putString(Engine.KEY_PARAM_LANGUAGE, language);
1129                    mParams.putString(Engine.KEY_PARAM_COUNTRY, country);
1130                    mParams.putString(Engine.KEY_PARAM_VARIANT, variant);
1131                }
1132                return result;
1133            }
1134        }, LANG_NOT_SUPPORTED, "setLanguage");
1135    }
1136
1137    /**
1138     * Returns a Locale instance describing the language currently being used for synthesis
1139     * requests sent to the TextToSpeech engine.
1140     *
1141     * In Android 4.2 and before (API <= 17) this function returns the language that is currently
1142     * being used by the TTS engine. That is the last language set by this or any other
1143     * client by a {@link TextToSpeech#setLanguage} call to the same engine.
1144     *
1145     * In Android versions after 4.2 this function returns the language that is currently being
1146     * used for the synthesis requests sent from this client. That is the last language set
1147     * by a {@link TextToSpeech#setLanguage} call on this instance.
1148     *
1149     * @return language, country (if any) and variant (if any) used by the client stored in a
1150     *     Locale instance, or {@code null} on error.
1151     */
1152    public Locale getLanguage() {
1153        return runAction(new Action<Locale>() {
1154            @Override
1155            public Locale run(ITextToSpeechService service) {
1156                /* No service call, but we're accessing mParams, hence need for
1157                   wrapping it as an Action instance */
1158                String lang = mParams.getString(Engine.KEY_PARAM_LANGUAGE, "");
1159                String country = mParams.getString(Engine.KEY_PARAM_COUNTRY, "");
1160                String variant = mParams.getString(Engine.KEY_PARAM_VARIANT, "");
1161                return new Locale(lang, country, variant);
1162            }
1163        }, null, "getLanguage");
1164    }
1165
1166    /**
1167     * Checks if the specified language as represented by the Locale is available and supported.
1168     *
1169     * @param loc The Locale describing the language to be used.
1170     *
1171     * @return Code indicating the support status for the locale. See {@link #LANG_AVAILABLE},
1172     *         {@link #LANG_COUNTRY_AVAILABLE}, {@link #LANG_COUNTRY_VAR_AVAILABLE},
1173     *         {@link #LANG_MISSING_DATA} and {@link #LANG_NOT_SUPPORTED}.
1174     */
1175    public int isLanguageAvailable(final Locale loc) {
1176        return runAction(new Action<Integer>() {
1177            @Override
1178            public Integer run(ITextToSpeechService service) throws RemoteException {
1179                return service.isLanguageAvailable(loc.getISO3Language(),
1180                        loc.getISO3Country(), loc.getVariant());
1181            }
1182        }, LANG_NOT_SUPPORTED, "isLanguageAvailable");
1183    }
1184
1185    /**
1186     * Synthesizes the given text to a file using the specified parameters.
1187     * This method is asynchronous, i.e. the method just adds the request to the queue of TTS
1188     * requests and then returns. The synthesis might not have finished (or even started!) at the
1189     * time when this method returns. In order to reliably detect errors during synthesis,
1190     * we recommend setting an utterance progress listener (see
1191     * {@link #setOnUtteranceProgressListener}) and using the
1192     * {@link Engine#KEY_PARAM_UTTERANCE_ID} parameter.
1193     *
1194     * @param text The text that should be synthesized. No longer than
1195     *            {@link #getMaxSpeechInputLength()} characters.
1196     * @param params Parameters for the request. Can be null.
1197     *            Supported parameter names:
1198     *            {@link Engine#KEY_PARAM_UTTERANCE_ID}.
1199     *            Engine specific parameters may be passed in but the parameter keys
1200     *            must be prefixed by the name of the engine they are intended for. For example
1201     *            the keys "com.svox.pico_foo" and "com.svox.pico:bar" will be passed to the
1202     *            engine named "com.svox.pico" if it is being used.
1203     * @param filename Absolute file filename to write the generated audio data to.It should be
1204     *            something like "/sdcard/myappsounds/mysound.wav".
1205     *
1206     * @return {@link #ERROR} or {@link #SUCCESS} of <b>queuing</b> the synthesizeToFile operation.
1207     */
1208    public int synthesizeToFile(final String text, final HashMap<String, String> params,
1209            final String filename) {
1210        return runAction(new Action<Integer>() {
1211            @Override
1212            public Integer run(ITextToSpeechService service) throws RemoteException {
1213                return service.synthesizeToFile(getCallerIdentity(), text, filename,
1214                        getParams(params));
1215            }
1216        }, ERROR, "synthesizeToFile");
1217    }
1218
1219    private Bundle getParams(HashMap<String, String> params) {
1220        if (params != null && !params.isEmpty()) {
1221            Bundle bundle = new Bundle(mParams);
1222            copyIntParam(bundle, params, Engine.KEY_PARAM_STREAM);
1223            copyStringParam(bundle, params, Engine.KEY_PARAM_UTTERANCE_ID);
1224            copyFloatParam(bundle, params, Engine.KEY_PARAM_VOLUME);
1225            copyFloatParam(bundle, params, Engine.KEY_PARAM_PAN);
1226
1227            // Copy feature strings defined by the framework.
1228            copyStringParam(bundle, params, Engine.KEY_FEATURE_NETWORK_SYNTHESIS);
1229            copyStringParam(bundle, params, Engine.KEY_FEATURE_EMBEDDED_SYNTHESIS);
1230
1231            // Copy over all parameters that start with the name of the
1232            // engine that we are currently connected to. The engine is
1233            // free to interpret them as it chooses.
1234            if (!TextUtils.isEmpty(mCurrentEngine)) {
1235                for (Map.Entry<String, String> entry : params.entrySet()) {
1236                    final String key = entry.getKey();
1237                    if (key != null && key.startsWith(mCurrentEngine)) {
1238                        bundle.putString(key, entry.getValue());
1239                    }
1240                }
1241            }
1242
1243            return bundle;
1244        } else {
1245            return mParams;
1246        }
1247    }
1248
1249    private void copyStringParam(Bundle bundle, HashMap<String, String> params, String key) {
1250        String value = params.get(key);
1251        if (value != null) {
1252            bundle.putString(key, value);
1253        }
1254    }
1255
1256    private void copyIntParam(Bundle bundle, HashMap<String, String> params, String key) {
1257        String valueString = params.get(key);
1258        if (!TextUtils.isEmpty(valueString)) {
1259            try {
1260                int value = Integer.parseInt(valueString);
1261                bundle.putInt(key, value);
1262            } catch (NumberFormatException ex) {
1263                // don't set the value in the bundle
1264            }
1265        }
1266    }
1267
1268    private void copyFloatParam(Bundle bundle, HashMap<String, String> params, String key) {
1269        String valueString = params.get(key);
1270        if (!TextUtils.isEmpty(valueString)) {
1271            try {
1272                float value = Float.parseFloat(valueString);
1273                bundle.putFloat(key, value);
1274            } catch (NumberFormatException ex) {
1275                // don't set the value in the bundle
1276            }
1277        }
1278    }
1279
1280    /**
1281     * Sets the listener that will be notified when synthesis of an utterance completes.
1282     *
1283     * @param listener The listener to use.
1284     *
1285     * @return {@link #ERROR} or {@link #SUCCESS}.
1286     *
1287     * @deprecated Use {@link #setOnUtteranceProgressListener(UtteranceProgressListener)}
1288     *        instead.
1289     */
1290    @Deprecated
1291    public int setOnUtteranceCompletedListener(final OnUtteranceCompletedListener listener) {
1292        mUtteranceProgressListener = UtteranceProgressListener.from(listener);
1293        return TextToSpeech.SUCCESS;
1294    }
1295
1296    /**
1297     * Sets the listener that will be notified of various events related to the
1298     * synthesis of a given utterance.
1299     *
1300     * See {@link UtteranceProgressListener} and
1301     * {@link TextToSpeech.Engine#KEY_PARAM_UTTERANCE_ID}.
1302     *
1303     * @param listener the listener to use.
1304     * @return {@link #ERROR} or {@link #SUCCESS}
1305     */
1306    public int setOnUtteranceProgressListener(UtteranceProgressListener listener) {
1307        mUtteranceProgressListener = listener;
1308        return TextToSpeech.SUCCESS;
1309    }
1310
1311    /**
1312     * Sets the TTS engine to use.
1313     *
1314     * @deprecated This doesn't inform callers when the TTS engine has been
1315     *        initialized. {@link #TextToSpeech(Context, OnInitListener, String)}
1316     *        can be used with the appropriate engine name. Also, there is no
1317     *        guarantee that the engine specified will be loaded. If it isn't
1318     *        installed or disabled, the user / system wide defaults will apply.
1319     *
1320     * @param enginePackageName The package name for the synthesis engine (e.g. "com.svox.pico")
1321     *
1322     * @return {@link #ERROR} or {@link #SUCCESS}.
1323     */
1324    @Deprecated
1325    public int setEngineByPackageName(String enginePackageName) {
1326        mRequestedEngine = enginePackageName;
1327        return initTts();
1328    }
1329
1330    /**
1331     * Gets the package name of the default speech synthesis engine.
1332     *
1333     * @return Package name of the TTS engine that the user has chosen
1334     *        as their default.
1335     */
1336    public String getDefaultEngine() {
1337        return mEnginesHelper.getDefaultEngine();
1338    }
1339
1340    /**
1341     * Checks whether the user's settings should override settings requested
1342     * by the calling application. As of the Ice cream sandwich release,
1343     * user settings never forcibly override the app's settings.
1344     */
1345    public boolean areDefaultsEnforced() {
1346        return false;
1347    }
1348
1349    /**
1350     * Gets a list of all installed TTS engines.
1351     *
1352     * @return A list of engine info objects. The list can be empty, but never {@code null}.
1353     */
1354    public List<EngineInfo> getEngines() {
1355        return mEnginesHelper.getEngines();
1356    }
1357
1358    private class Connection implements ServiceConnection {
1359        private ITextToSpeechService mService;
1360
1361        private OnServiceConnectedAsyncTask mOnServiceConnectedAsyncTask;
1362
1363        private final ITextToSpeechCallback.Stub mCallback = new ITextToSpeechCallback.Stub() {
1364            @Override
1365            public void onDone(String utteranceId) {
1366                UtteranceProgressListener listener = mUtteranceProgressListener;
1367                if (listener != null) {
1368                    listener.onDone(utteranceId);
1369                }
1370            }
1371
1372            @Override
1373            public void onError(String utteranceId) {
1374                UtteranceProgressListener listener = mUtteranceProgressListener;
1375                if (listener != null) {
1376                    listener.onError(utteranceId);
1377                }
1378            }
1379
1380            @Override
1381            public void onStart(String utteranceId) {
1382                UtteranceProgressListener listener = mUtteranceProgressListener;
1383                if (listener != null) {
1384                    listener.onStart(utteranceId);
1385                }
1386            }
1387        };
1388
1389        private class OnServiceConnectedAsyncTask extends AsyncTask<Void, Void, Integer> {
1390            private final ComponentName mName;
1391            private final ITextToSpeechService mConnectedService;
1392
1393            public OnServiceConnectedAsyncTask(ComponentName name, IBinder service) {
1394                mName = name;
1395                mConnectedService = ITextToSpeechService.Stub.asInterface(service);
1396            }
1397
1398            @Override
1399            protected Integer doInBackground(Void... params) {
1400                synchronized(mStartLock) {
1401                    if (isCancelled()) {
1402                        return null;
1403                    }
1404
1405                    try {
1406                        mConnectedService.setCallback(getCallerIdentity(), mCallback);
1407                        String[] defaultLanguage = mConnectedService.getClientDefaultLanguage();
1408
1409                        mParams.putString(Engine.KEY_PARAM_LANGUAGE, defaultLanguage[0]);
1410                        mParams.putString(Engine.KEY_PARAM_COUNTRY, defaultLanguage[1]);
1411                        mParams.putString(Engine.KEY_PARAM_VARIANT, defaultLanguage[2]);
1412
1413                        Log.i(TAG, "Set up connection to " + mName);
1414                        return SUCCESS;
1415                    } catch (RemoteException re) {
1416                        Log.e(TAG, "Error connecting to service, setCallback() failed");
1417                        return ERROR;
1418                    }
1419                }
1420            }
1421
1422            @Override
1423            protected void onPostExecute(Integer result) {
1424                synchronized(mStartLock) {
1425                    if (mOnServiceConnectedAsyncTask == this) {
1426                        mOnServiceConnectedAsyncTask = null;
1427                    }
1428
1429                    mServiceConnection = Connection.this;
1430                    mService = mConnectedService;
1431
1432                    dispatchOnInit(result);
1433                }
1434            }
1435        }
1436
1437        @Override
1438        public void onServiceConnected(ComponentName name, IBinder service) {
1439            synchronized(mStartLock) {
1440                Log.i(TAG, "Connected to " + name);
1441
1442                if (mOnServiceConnectedAsyncTask != null) {
1443                    mOnServiceConnectedAsyncTask.cancel(false);
1444                }
1445
1446                mOnServiceConnectedAsyncTask = new OnServiceConnectedAsyncTask(name, service);
1447                mOnServiceConnectedAsyncTask.execute();
1448            }
1449        }
1450
1451        public IBinder getCallerIdentity() {
1452            return mCallback;
1453        }
1454
1455        /**
1456         * Clear connection related fields and cancel mOnServiceConnectedAsyncTask if set.
1457         *
1458         * @return true if we cancel mOnServiceConnectedAsyncTask in progress.
1459         */
1460        private boolean clearServiceConnection() {
1461            synchronized(mStartLock) {
1462                boolean result = false;
1463                if (mOnServiceConnectedAsyncTask != null) {
1464                    result = mOnServiceConnectedAsyncTask.cancel(false);
1465                    mOnServiceConnectedAsyncTask = null;
1466                }
1467
1468                mService = null;
1469                // If this is the active connection, clear it
1470                if (mServiceConnection == this) {
1471                    mServiceConnection = null;
1472                }
1473                return result;
1474            }
1475        }
1476
1477        @Override
1478        public void onServiceDisconnected(ComponentName name) {
1479            Log.i(TAG, "Asked to disconnect from " + name);
1480            if (clearServiceConnection()) {
1481                /* We need to protect against a rare case where engine
1482                 * dies just after successful connection - and we process onServiceDisconnected
1483                 * before OnServiceConnectedAsyncTask.onPostExecute. onServiceDisconnected cancels
1484                 * OnServiceConnectedAsyncTask.onPostExecute and we don't call dispatchOnInit
1485                 * with ERROR as argument.
1486                 */
1487                dispatchOnInit(ERROR);
1488            }
1489        }
1490
1491        public void disconnect() {
1492            mContext.unbindService(this);
1493            clearServiceConnection();
1494        }
1495
1496        public <R> R runAction(Action<R> action, R errorResult, String method, boolean reconnect) {
1497            synchronized (mStartLock) {
1498                try {
1499                    if (mService == null) {
1500                        Log.w(TAG, method + " failed: not connected to TTS engine");
1501                        return errorResult;
1502                    }
1503                    return action.run(mService);
1504                } catch (RemoteException ex) {
1505                    Log.e(TAG, method + " failed", ex);
1506                    if (reconnect) {
1507                        disconnect();
1508                        initTts();
1509                    }
1510                    return errorResult;
1511                }
1512            }
1513        }
1514    }
1515
1516    private interface Action<R> {
1517        R run(ITextToSpeechService service) throws RemoteException;
1518    }
1519
1520    /**
1521     * Information about an installed text-to-speech engine.
1522     *
1523     * @see TextToSpeech#getEngines
1524     */
1525    public static class EngineInfo {
1526        /**
1527         * Engine package name..
1528         */
1529        public String name;
1530        /**
1531         * Localized label for the engine.
1532         */
1533        public String label;
1534        /**
1535         * Icon for the engine.
1536         */
1537        public int icon;
1538        /**
1539         * Whether this engine is a part of the system
1540         * image.
1541         *
1542         * @hide
1543         */
1544        public boolean system;
1545        /**
1546         * The priority the engine declares for the the intent filter
1547         * {@code android.intent.action.TTS_SERVICE}
1548         *
1549         * @hide
1550         */
1551        public int priority;
1552
1553        @Override
1554        public String toString() {
1555            return "EngineInfo{name=" + name + "}";
1556        }
1557
1558    }
1559
1560    /**
1561     * Limit of length of input string passed to speak and synthesizeToFile.
1562     *
1563     * @see #speak
1564     * @see #synthesizeToFile
1565     */
1566    public static int getMaxSpeechInputLength() {
1567        return 4000;
1568    }
1569}
1570