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