TextToSpeech.java revision 754c72ed9e8e83e5a913aa7552fc2e1b1b5277e0
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    private final Map<String, Uri> mEarcons;
490    private final Map<String, Uri> mUtterances;
491    private final Bundle mParams = new Bundle();
492    private final TtsEngines mEnginesHelper;
493    private volatile String mCurrentEngine = null;
494
495    /**
496     * The constructor for the TextToSpeech class, using the default TTS engine.
497     * This will also initialize the associated TextToSpeech engine if it isn't already running.
498     *
499     * @param context
500     *            The context this instance is running in.
501     * @param listener
502     *            The {@link TextToSpeech.OnInitListener} that will be called when the
503     *            TextToSpeech engine has initialized.
504     */
505    public TextToSpeech(Context context, OnInitListener listener) {
506        this(context, listener, null);
507    }
508
509    /**
510     * The constructor for the TextToSpeech class, using the given TTS engine.
511     * This will also initialize the associated TextToSpeech engine if it isn't already running.
512     *
513     * @param context
514     *            The context this instance is running in.
515     * @param listener
516     *            The {@link TextToSpeech.OnInitListener} that will be called when the
517     *            TextToSpeech engine has initialized.
518     * @param engine Package name of the TTS engine to use.
519     */
520    public TextToSpeech(Context context, OnInitListener listener, String engine) {
521        mContext = context;
522        mInitListener = listener;
523        mRequestedEngine = engine;
524
525        mEarcons = new HashMap<String, Uri>();
526        mUtterances = new HashMap<String, Uri>();
527
528        mEnginesHelper = new TtsEngines(mContext);
529        initTts();
530    }
531
532    private String getPackageName() {
533        return mContext.getPackageName();
534    }
535
536    private <R> R runActionNoReconnect(Action<R> action, R errorResult, String method) {
537        return runAction(action, errorResult, method, false);
538    }
539
540    private <R> R runAction(Action<R> action, R errorResult, String method) {
541        return runAction(action, errorResult, method, true);
542    }
543
544    private <R> R runAction(Action<R> action, R errorResult, String method, boolean reconnect) {
545        synchronized (mStartLock) {
546            if (mServiceConnection == null) {
547                Log.w(TAG, method + " failed: not bound to TTS engine");
548                return errorResult;
549            }
550            return mServiceConnection.runAction(action, errorResult, method, reconnect);
551        }
552    }
553
554    private int initTts() {
555        // Step 1: Try connecting to the engine that was requested.
556        if (mRequestedEngine != null && mEnginesHelper.isEngineInstalled(mRequestedEngine)) {
557            if (connectToEngine(mRequestedEngine)) {
558                mCurrentEngine = mRequestedEngine;
559                return SUCCESS;
560            }
561        }
562
563        // Step 2: Try connecting to the user's default engine.
564        final String defaultEngine = getDefaultEngine();
565        if (defaultEngine != null && !defaultEngine.equals(mRequestedEngine)) {
566            if (connectToEngine(defaultEngine)) {
567                mCurrentEngine = defaultEngine;
568                return SUCCESS;
569            }
570        }
571
572        // Step 3: Try connecting to the highest ranked engine in the
573        // system.
574        final String highestRanked = mEnginesHelper.getHighestRankedEngineName();
575        if (highestRanked != null && !highestRanked.equals(mRequestedEngine) &&
576                !highestRanked.equals(defaultEngine)) {
577            if (connectToEngine(highestRanked)) {
578                mCurrentEngine = highestRanked;
579                return SUCCESS;
580            }
581        }
582
583        // NOTE: The API currently does not allow the caller to query whether
584        // they are actually connected to any engine. This might fail for various
585        // reasons like if the user disables all her TTS engines.
586
587        mCurrentEngine = null;
588        dispatchOnInit(ERROR);
589        return ERROR;
590    }
591
592    private boolean connectToEngine(String engine) {
593        Connection connection = new Connection();
594        Intent intent = new Intent(Engine.INTENT_ACTION_TTS_SERVICE);
595        intent.setPackage(engine);
596        boolean bound = mContext.bindService(intent, connection, Context.BIND_AUTO_CREATE);
597        if (!bound) {
598            Log.e(TAG, "Failed to bind to " + engine);
599            return false;
600        } else {
601            Log.i(TAG, "Sucessfully bound to " + engine);
602            return true;
603        }
604    }
605
606    private void dispatchOnInit(int result) {
607        synchronized (mStartLock) {
608            if (mInitListener != null) {
609                mInitListener.onInit(result);
610                mInitListener = null;
611            }
612        }
613    }
614
615    /**
616     * Releases the resources used by the TextToSpeech engine.
617     * It is good practice for instance to call this method in the onDestroy() method of an Activity
618     * so the TextToSpeech engine can be cleanly stopped.
619     */
620    public void shutdown() {
621        runActionNoReconnect(new Action<Void>() {
622            @Override
623            public Void run(ITextToSpeechService service) throws RemoteException {
624                service.setCallback(getPackageName(), null);
625                service.stop(getPackageName());
626                mServiceConnection.disconnect();
627                // Context#unbindService does not result in a call to
628                // ServiceConnection#onServiceDisconnected. As a result, the
629                // service ends up being destroyed (if there are no other open
630                // connections to it) but the process lives on and the
631                // ServiceConnection continues to refer to the destroyed service.
632                //
633                // This leads to tons of log spam about SynthThread being dead.
634                mServiceConnection = null;
635                mCurrentEngine = null;
636                return null;
637            }
638        }, null, "shutdown");
639    }
640
641    /**
642     * Adds a mapping between a string of text and a sound resource in a
643     * package. After a call to this method, subsequent calls to
644     * {@link #speak(String, int, HashMap)} will play the specified sound resource
645     * if it is available, or synthesize the text it is missing.
646     *
647     * @param text
648     *            The string of text. Example: <code>"south_south_east"</code>
649     *
650     * @param packagename
651     *            Pass the packagename of the application that contains the
652     *            resource. If the resource is in your own application (this is
653     *            the most common case), then put the packagename of your
654     *            application here.<br/>
655     *            Example: <b>"com.google.marvin.compass"</b><br/>
656     *            The packagename can be found in the AndroidManifest.xml of
657     *            your application.
658     *            <p>
659     *            <code>&lt;manifest xmlns:android=&quot;...&quot;
660     *      package=&quot;<b>com.google.marvin.compass</b>&quot;&gt;</code>
661     *            </p>
662     *
663     * @param resourceId
664     *            Example: <code>R.raw.south_south_east</code>
665     *
666     * @return Code indicating success or failure. See {@link #ERROR} and {@link #SUCCESS}.
667     */
668    public int addSpeech(String text, String packagename, int resourceId) {
669        synchronized (mStartLock) {
670            mUtterances.put(text, makeResourceUri(packagename, resourceId));
671            return SUCCESS;
672        }
673    }
674
675    /**
676     * Adds a mapping between a string of text and a sound file. Using this, it
677     * is possible to add custom pronounciations for a string of text.
678     * After a call to this method, subsequent calls to {@link #speak(String, int, HashMap)}
679     * will play the specified sound resource if it is available, or synthesize the text it is
680     * missing.
681     *
682     * @param text
683     *            The string of text. Example: <code>"south_south_east"</code>
684     * @param filename
685     *            The full path to the sound file (for example:
686     *            "/sdcard/mysounds/hello.wav")
687     *
688     * @return Code indicating success or failure. See {@link #ERROR} and {@link #SUCCESS}.
689     */
690    public int addSpeech(String text, String filename) {
691        synchronized (mStartLock) {
692            mUtterances.put(text, Uri.parse(filename));
693            return SUCCESS;
694        }
695    }
696
697
698    /**
699     * Adds a mapping between a string of text and a sound resource in a
700     * package. Use this to add custom earcons.
701     *
702     * @see #playEarcon(String, int, HashMap)
703     *
704     * @param earcon The name of the earcon.
705     *            Example: <code>"[tick]"</code><br/>
706     *
707     * @param packagename
708     *            the package name of the application that contains the
709     *            resource. This can for instance be the package name of your own application.
710     *            Example: <b>"com.google.marvin.compass"</b><br/>
711     *            The package name can be found in the AndroidManifest.xml of
712     *            the application containing the resource.
713     *            <p>
714     *            <code>&lt;manifest xmlns:android=&quot;...&quot;
715     *      package=&quot;<b>com.google.marvin.compass</b>&quot;&gt;</code>
716     *            </p>
717     *
718     * @param resourceId
719     *            Example: <code>R.raw.tick_snd</code>
720     *
721     * @return Code indicating success or failure. See {@link #ERROR} and {@link #SUCCESS}.
722     */
723    public int addEarcon(String earcon, String packagename, int resourceId) {
724        synchronized(mStartLock) {
725            mEarcons.put(earcon, makeResourceUri(packagename, resourceId));
726            return SUCCESS;
727        }
728    }
729
730    /**
731     * Adds a mapping between a string of text and a sound file.
732     * Use this to add custom earcons.
733     *
734     * @see #playEarcon(String, int, HashMap)
735     *
736     * @param earcon
737     *            The name of the earcon.
738     *            Example: <code>"[tick]"</code>
739     * @param filename
740     *            The full path to the sound file (for example:
741     *            "/sdcard/mysounds/tick.wav")
742     *
743     * @return Code indicating success or failure. See {@link #ERROR} and {@link #SUCCESS}.
744     */
745    public int addEarcon(String earcon, String filename) {
746        synchronized(mStartLock) {
747            mEarcons.put(earcon, Uri.parse(filename));
748            return SUCCESS;
749        }
750    }
751
752    private Uri makeResourceUri(String packageName, int resourceId) {
753        return new Uri.Builder()
754                .scheme(ContentResolver.SCHEME_ANDROID_RESOURCE)
755                .encodedAuthority(packageName)
756                .appendEncodedPath(String.valueOf(resourceId))
757                .build();
758    }
759
760    /**
761     * Speaks the string using the specified queuing strategy and speech
762     * parameters.
763     *
764     * @param text The string of text to be spoken.
765     * @param queueMode The queuing strategy to use, {@link #QUEUE_ADD} or {@link #QUEUE_FLUSH}.
766     * @param params Parameters for the request. Can be null.
767     *            Supported parameter names:
768     *            {@link Engine#KEY_PARAM_STREAM},
769     *            {@link Engine#KEY_PARAM_UTTERANCE_ID},
770     *            {@link Engine#KEY_PARAM_VOLUME},
771     *            {@link Engine#KEY_PARAM_PAN}.
772     *            Engine specific parameters may be passed in but the parameter keys
773     *            must be prefixed by the name of the engine they are intended for. For example
774     *            the keys "com.svox.pico_foo" and "com.svox.pico:bar" will be passed to the
775     *            engine named "com.svox.pico" if it is being used.
776     *
777     * @return {@link #ERROR} or {@link #SUCCESS}.
778     */
779    public int speak(final String text, final int queueMode, final HashMap<String, String> params) {
780        return runAction(new Action<Integer>() {
781            @Override
782            public Integer run(ITextToSpeechService service) throws RemoteException {
783                Uri utteranceUri = mUtterances.get(text);
784                if (utteranceUri != null) {
785                    return service.playAudio(getPackageName(), utteranceUri, queueMode,
786                            getParams(params));
787                } else {
788                    return service.speak(getPackageName(), text, queueMode, getParams(params));
789                }
790            }
791        }, ERROR, "speak");
792    }
793
794    /**
795     * Plays the earcon using the specified queueing mode and parameters.
796     * The earcon must already have been added with {@link #addEarcon(String, String)} or
797     * {@link #addEarcon(String, String, int)}.
798     *
799     * @param earcon The earcon that should be played
800     * @param queueMode {@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     *            Engine specific parameters may be passed in but the parameter keys
806     *            must be prefixed by the name of the engine they are intended for. For example
807     *            the keys "com.svox.pico_foo" and "com.svox.pico:bar" will be passed to the
808     *            engine named "com.svox.pico" if it is being used.
809     *
810     * @return {@link #ERROR} or {@link #SUCCESS}.
811     */
812    public int playEarcon(final String earcon, final int queueMode,
813            final HashMap<String, String> params) {
814        return runAction(new Action<Integer>() {
815            @Override
816            public Integer run(ITextToSpeechService service) throws RemoteException {
817                Uri earconUri = mEarcons.get(earcon);
818                if (earconUri == null) {
819                    return ERROR;
820                }
821                return service.playAudio(getPackageName(), earconUri, queueMode,
822                        getParams(params));
823            }
824        }, ERROR, "playEarcon");
825    }
826
827    /**
828     * Plays silence for the specified amount of time using the specified
829     * queue mode.
830     *
831     * @param durationInMs The duration of the silence.
832     * @param queueMode {@link #QUEUE_ADD} or {@link #QUEUE_FLUSH}.
833     * @param params Parameters for the request. Can be null.
834     *            Supported parameter names:
835     *            {@link Engine#KEY_PARAM_UTTERANCE_ID}.
836     *            Engine specific parameters may be passed in but the parameter keys
837     *            must be prefixed by the name of the engine they are intended for. For example
838     *            the keys "com.svox.pico_foo" and "com.svox.pico:bar" will be passed to the
839     *            engine named "com.svox.pico" if it is being used.
840     *
841     * @return {@link #ERROR} or {@link #SUCCESS}.
842     */
843    public int playSilence(final long durationInMs, final int queueMode,
844            final HashMap<String, String> params) {
845        return runAction(new Action<Integer>() {
846            @Override
847            public Integer run(ITextToSpeechService service) throws RemoteException {
848                return service.playSilence(getPackageName(), durationInMs, queueMode,
849                        getParams(params));
850            }
851        }, ERROR, "playSilence");
852    }
853
854    /**
855     * Queries the engine for the set of features it supports for a given locale.
856     * Features can either be framework defined, e.g.
857     * {@link TextToSpeech.Engine#KEY_FEATURE_NETWORK_SYNTHESIS} or engine specific.
858     * Engine specific keys must be prefixed by the name of the engine they
859     * are intended for. These keys can be used as parameters to
860     * {@link TextToSpeech#speak(String, int, java.util.HashMap)} and
861     * {@link TextToSpeech#synthesizeToFile(String, java.util.HashMap, String)}.
862     *
863     * Features are boolean flags, and their values in the synthesis parameters
864     * must be behave as per {@link Boolean#parseBoolean(String)}.
865     *
866     * @param locale The locale to query features for.
867     */
868    public Set<String> getFeatures(final Locale locale) {
869        return runAction(new Action<Set<String>>() {
870            @Override
871            public Set<String> run(ITextToSpeechService service) throws RemoteException {
872                String[] features = service.getFeaturesForLanguage(
873                        locale.getISO3Language(), locale.getISO3Country(), locale.getVariant());
874                if (features != null) {
875                    final Set<String> featureSet = new HashSet<String>();
876                    Collections.addAll(featureSet, features);
877                    return featureSet;
878                }
879                return null;
880            }
881        }, null, "getFeatures");
882    }
883
884    /**
885     * Checks whether the TTS engine is busy speaking. Note that a speech item is
886     * considered complete once it's audio data has been sent to the audio mixer, or
887     * written to a file. There might be a finite lag between this point, and when
888     * the audio hardware completes playback.
889     *
890     * @return {@code true} if the TTS engine is speaking.
891     */
892    public boolean isSpeaking() {
893        return runAction(new Action<Boolean>() {
894            @Override
895            public Boolean run(ITextToSpeechService service) throws RemoteException {
896                return service.isSpeaking();
897            }
898        }, false, "isSpeaking");
899    }
900
901    /**
902     * Interrupts the current utterance (whether played or rendered to file) and discards other
903     * utterances in the queue.
904     *
905     * @return {@link #ERROR} or {@link #SUCCESS}.
906     */
907    public int stop() {
908        return runAction(new Action<Integer>() {
909            @Override
910            public Integer run(ITextToSpeechService service) throws RemoteException {
911                return service.stop(getPackageName());
912            }
913        }, ERROR, "stop");
914    }
915
916    /**
917     * Sets the speech rate.
918     *
919     * This has no effect on any pre-recorded speech.
920     *
921     * @param speechRate Speech rate. {@code 1.0} is the normal speech rate,
922     *            lower values slow down the speech ({@code 0.5} is half the normal speech rate),
923     *            greater values accelerate it ({@code 2.0} is twice the normal speech rate).
924     *
925     * @return {@link #ERROR} or {@link #SUCCESS}.
926     */
927    public int setSpeechRate(float speechRate) {
928        if (speechRate > 0.0f) {
929            int intRate = (int)(speechRate * 100);
930            if (intRate > 0) {
931                synchronized (mStartLock) {
932                    mParams.putInt(Engine.KEY_PARAM_RATE, intRate);
933                }
934                return SUCCESS;
935            }
936        }
937        return ERROR;
938    }
939
940    /**
941     * Sets the speech pitch for the TextToSpeech engine.
942     *
943     * This has no effect on any pre-recorded speech.
944     *
945     * @param pitch Speech pitch. {@code 1.0} is the normal pitch,
946     *            lower values lower the tone of the synthesized voice,
947     *            greater values increase it.
948     *
949     * @return {@link #ERROR} or {@link #SUCCESS}.
950     */
951    public int setPitch(float pitch) {
952        if (pitch > 0.0f) {
953            int intPitch = (int)(pitch * 100);
954            if (intPitch > 0) {
955                synchronized (mStartLock) {
956                    mParams.putInt(Engine.KEY_PARAM_PITCH, intPitch);
957                }
958                return SUCCESS;
959            }
960        }
961        return ERROR;
962    }
963
964    /**
965     * @return the engine currently in use by this TextToSpeech instance.
966     * @hide
967     */
968    public String getCurrentEngine() {
969        return mCurrentEngine;
970    }
971
972    /**
973     * Sets the text-to-speech language.
974     * The TTS engine will try to use the closest match to the specified
975     * language as represented by the Locale, but there is no guarantee that the exact same Locale
976     * will be used. Use {@link #isLanguageAvailable(Locale)} to check the level of support
977     * before choosing the language to use for the next utterances.
978     *
979     * @param loc The locale describing the language to be used.
980     *
981     * @return Code indicating the support status for the locale. See {@link #LANG_AVAILABLE},
982     *         {@link #LANG_COUNTRY_AVAILABLE}, {@link #LANG_COUNTRY_VAR_AVAILABLE},
983     *         {@link #LANG_MISSING_DATA} and {@link #LANG_NOT_SUPPORTED}.
984     */
985    public int setLanguage(final Locale loc) {
986        return runAction(new Action<Integer>() {
987            @Override
988            public Integer run(ITextToSpeechService service) throws RemoteException {
989                if (loc == null) {
990                    return LANG_NOT_SUPPORTED;
991                }
992                String language = loc.getISO3Language();
993                String country = loc.getISO3Country();
994                String variant = loc.getVariant();
995                // Check if the language, country, variant are available, and cache
996                // the available parts.
997                // Note that the language is not actually set here, instead it is cached so it
998                // will be associated with all upcoming utterances.
999                int result = service.loadLanguage(language, country, variant);
1000                if (result >= LANG_AVAILABLE){
1001                    if (result < LANG_COUNTRY_VAR_AVAILABLE) {
1002                        variant = "";
1003                        if (result < LANG_COUNTRY_AVAILABLE) {
1004                            country = "";
1005                        }
1006                    }
1007                    mParams.putString(Engine.KEY_PARAM_LANGUAGE, language);
1008                    mParams.putString(Engine.KEY_PARAM_COUNTRY, country);
1009                    mParams.putString(Engine.KEY_PARAM_VARIANT, variant);
1010                }
1011                return result;
1012            }
1013        }, LANG_NOT_SUPPORTED, "setLanguage");
1014    }
1015
1016    /**
1017     * Returns a Locale instance describing the language currently being used by the TextToSpeech
1018     * engine.
1019     *
1020     * @return language, country (if any) and variant (if any) used by the engine stored in a Locale
1021     *     instance, or {@code null} on error.
1022     */
1023    public Locale getLanguage() {
1024        return runAction(new Action<Locale>() {
1025            @Override
1026            public Locale run(ITextToSpeechService service) throws RemoteException {
1027                String[] locStrings = service.getLanguage();
1028                if (locStrings != null && locStrings.length == 3) {
1029                    return new Locale(locStrings[0], locStrings[1], locStrings[2]);
1030                }
1031                return null;
1032            }
1033        }, null, "getLanguage");
1034    }
1035
1036    /**
1037     * Checks if the specified language as represented by the Locale is available and supported.
1038     *
1039     * @param loc The Locale describing the language to be used.
1040     *
1041     * @return Code indicating the support status for the locale. See {@link #LANG_AVAILABLE},
1042     *         {@link #LANG_COUNTRY_AVAILABLE}, {@link #LANG_COUNTRY_VAR_AVAILABLE},
1043     *         {@link #LANG_MISSING_DATA} and {@link #LANG_NOT_SUPPORTED}.
1044     */
1045    public int isLanguageAvailable(final Locale loc) {
1046        return runAction(new Action<Integer>() {
1047            @Override
1048            public Integer run(ITextToSpeechService service) throws RemoteException {
1049                return service.isLanguageAvailable(loc.getISO3Language(),
1050                        loc.getISO3Country(), loc.getVariant());
1051            }
1052        }, LANG_NOT_SUPPORTED, "isLanguageAvailable");
1053    }
1054
1055    /**
1056     * Synthesizes the given text to a file using the specified parameters.
1057     *
1058     * @param text The text that should be synthesized
1059     * @param params Parameters for the request. Can be null.
1060     *            Supported parameter names:
1061     *            {@link Engine#KEY_PARAM_UTTERANCE_ID}.
1062     *            Engine specific parameters may be passed in but the parameter keys
1063     *            must be prefixed by the name of the engine they are intended for. For example
1064     *            the keys "com.svox.pico_foo" and "com.svox.pico:bar" will be passed to the
1065     *            engine named "com.svox.pico" if it is being used.
1066     * @param filename Absolute file filename to write the generated audio data to.It should be
1067     *            something like "/sdcard/myappsounds/mysound.wav".
1068     *
1069     * @return {@link #ERROR} or {@link #SUCCESS}.
1070     */
1071    public int synthesizeToFile(final String text, final HashMap<String, String> params,
1072            final String filename) {
1073        return runAction(new Action<Integer>() {
1074            @Override
1075            public Integer run(ITextToSpeechService service) throws RemoteException {
1076                return service.synthesizeToFile(getPackageName(), text, filename,
1077                        getParams(params));
1078            }
1079        }, ERROR, "synthesizeToFile");
1080    }
1081
1082    private Bundle getParams(HashMap<String, String> params) {
1083        if (params != null && !params.isEmpty()) {
1084            Bundle bundle = new Bundle(mParams);
1085            copyIntParam(bundle, params, Engine.KEY_PARAM_STREAM);
1086            copyStringParam(bundle, params, Engine.KEY_PARAM_UTTERANCE_ID);
1087            copyFloatParam(bundle, params, Engine.KEY_PARAM_VOLUME);
1088            copyFloatParam(bundle, params, Engine.KEY_PARAM_PAN);
1089
1090            // Copy feature strings defined by the framework.
1091            copyStringParam(bundle, params, Engine.KEY_FEATURE_NETWORK_SYNTHESIS);
1092            copyStringParam(bundle, params, Engine.KEY_FEATURE_EMBEDDED_SYNTHESIS);
1093
1094            // Copy over all parameters that start with the name of the
1095            // engine that we are currently connected to. The engine is
1096            // free to interpret them as it chooses.
1097            if (!TextUtils.isEmpty(mCurrentEngine)) {
1098                for (Map.Entry<String, String> entry : params.entrySet()) {
1099                    final String key = entry.getKey();
1100                    if (key != null && key.startsWith(mCurrentEngine)) {
1101                        bundle.putString(key, entry.getValue());
1102                    }
1103                }
1104            }
1105
1106            return bundle;
1107        } else {
1108            return mParams;
1109        }
1110    }
1111
1112    private void copyStringParam(Bundle bundle, HashMap<String, String> params, String key) {
1113        String value = params.get(key);
1114        if (value != null) {
1115            bundle.putString(key, value);
1116        }
1117    }
1118
1119    private void copyIntParam(Bundle bundle, HashMap<String, String> params, String key) {
1120        String valueString = params.get(key);
1121        if (!TextUtils.isEmpty(valueString)) {
1122            try {
1123                int value = Integer.parseInt(valueString);
1124                bundle.putInt(key, value);
1125            } catch (NumberFormatException ex) {
1126                // don't set the value in the bundle
1127            }
1128        }
1129    }
1130
1131    private void copyFloatParam(Bundle bundle, HashMap<String, String> params, String key) {
1132        String valueString = params.get(key);
1133        if (!TextUtils.isEmpty(valueString)) {
1134            try {
1135                float value = Float.parseFloat(valueString);
1136                bundle.putFloat(key, value);
1137            } catch (NumberFormatException ex) {
1138                // don't set the value in the bundle
1139            }
1140        }
1141    }
1142
1143    /**
1144     * Sets the listener that will be notified when synthesis of an utterance completes.
1145     *
1146     * @param listener The listener to use.
1147     *
1148     * @return {@link #ERROR} or {@link #SUCCESS}.
1149     *
1150     * @deprecated Use {@link #setOnUtteranceProgressListener(UtteranceProgressListener)}
1151     *        instead.
1152     */
1153    @Deprecated
1154    public int setOnUtteranceCompletedListener(final OnUtteranceCompletedListener listener) {
1155        mUtteranceProgressListener = UtteranceProgressListener.from(listener);
1156        return TextToSpeech.SUCCESS;
1157    }
1158
1159    /**
1160     * Sets the listener that will be notified of various events related to the
1161     * synthesis of a given utterance.
1162     *
1163     * See {@link UtteranceProgressListener} and
1164     * {@link TextToSpeech.Engine#KEY_PARAM_UTTERANCE_ID}.
1165     *
1166     * @param listener the listener to use.
1167     * @return {@link #ERROR} or {@link #SUCCESS}
1168     */
1169    public int setOnUtteranceProgressListener(UtteranceProgressListener listener) {
1170        mUtteranceProgressListener = listener;
1171        return TextToSpeech.SUCCESS;
1172    }
1173
1174    /**
1175     * Sets the TTS engine to use.
1176     *
1177     * @deprecated This doesn't inform callers when the TTS engine has been
1178     *        initialized. {@link #TextToSpeech(Context, OnInitListener, String)}
1179     *        can be used with the appropriate engine name. Also, there is no
1180     *        guarantee that the engine specified will be loaded. If it isn't
1181     *        installed or disabled, the user / system wide defaults will apply.
1182     *
1183     * @param enginePackageName The package name for the synthesis engine (e.g. "com.svox.pico")
1184     *
1185     * @return {@link #ERROR} or {@link #SUCCESS}.
1186     */
1187    @Deprecated
1188    public int setEngineByPackageName(String enginePackageName) {
1189        mRequestedEngine = enginePackageName;
1190        return initTts();
1191    }
1192
1193    /**
1194     * Gets the package name of the default speech synthesis engine.
1195     *
1196     * @return Package name of the TTS engine that the user has chosen
1197     *        as their default.
1198     */
1199    public String getDefaultEngine() {
1200        return mEnginesHelper.getDefaultEngine();
1201    }
1202
1203    /**
1204     * Checks whether the user's settings should override settings requested
1205     * by the calling application. As of the Ice cream sandwich release,
1206     * user settings never forcibly override the app's settings.
1207     */
1208    public boolean areDefaultsEnforced() {
1209        return false;
1210    }
1211
1212    /**
1213     * Gets a list of all installed TTS engines.
1214     *
1215     * @return A list of engine info objects. The list can be empty, but never {@code null}.
1216     */
1217    public List<EngineInfo> getEngines() {
1218        return mEnginesHelper.getEngines();
1219    }
1220
1221
1222    private class Connection implements ServiceConnection {
1223        private ITextToSpeechService mService;
1224        private final ITextToSpeechCallback.Stub mCallback = new ITextToSpeechCallback.Stub() {
1225            @Override
1226            public void onDone(String utteranceId) {
1227                UtteranceProgressListener listener = mUtteranceProgressListener;
1228                if (listener != null) {
1229                    listener.onDone(utteranceId);
1230                }
1231            }
1232
1233            @Override
1234            public void onError(String utteranceId) {
1235                UtteranceProgressListener listener = mUtteranceProgressListener;
1236                if (listener != null) {
1237                    listener.onError(utteranceId);
1238                }
1239            }
1240
1241            @Override
1242            public void onStart(String utteranceId) {
1243                UtteranceProgressListener listener = mUtteranceProgressListener;
1244                if (listener != null) {
1245                    listener.onStart(utteranceId);
1246                }
1247            }
1248        };
1249
1250        public void onServiceConnected(ComponentName name, IBinder service) {
1251            Log.i(TAG, "Connected to " + name);
1252            synchronized(mStartLock) {
1253                if (mServiceConnection != null) {
1254                    // Disconnect any previous service connection
1255                    mServiceConnection.disconnect();
1256                }
1257                mServiceConnection = this;
1258                mService = ITextToSpeechService.Stub.asInterface(service);
1259                try {
1260                    mService.setCallback(getPackageName(), mCallback);
1261                    dispatchOnInit(SUCCESS);
1262                } catch (RemoteException re) {
1263                    Log.e(TAG, "Error connecting to service, setCallback() failed");
1264                    dispatchOnInit(ERROR);
1265                }
1266            }
1267        }
1268
1269        public void onServiceDisconnected(ComponentName name) {
1270            synchronized(mStartLock) {
1271                mService = null;
1272                // If this is the active connection, clear it
1273                if (mServiceConnection == this) {
1274                    mServiceConnection = null;
1275                }
1276            }
1277        }
1278
1279        public void disconnect() {
1280            mContext.unbindService(this);
1281        }
1282
1283        public <R> R runAction(Action<R> action, R errorResult, String method, boolean reconnect) {
1284            try {
1285                synchronized (mStartLock) {
1286                    if (mService == null) {
1287                        Log.w(TAG, method + " failed: not connected to TTS engine");
1288                        return errorResult;
1289                    }
1290                    return action.run(mService);
1291                }
1292            } catch (RemoteException ex) {
1293                Log.e(TAG, method + " failed", ex);
1294                if (reconnect) {
1295                    disconnect();
1296                    initTts();
1297                }
1298                return errorResult;
1299            }
1300        }
1301    }
1302
1303    private interface Action<R> {
1304        R run(ITextToSpeechService service) throws RemoteException;
1305    }
1306
1307    /**
1308     * Information about an installed text-to-speech engine.
1309     *
1310     * @see TextToSpeech#getEngines
1311     */
1312    public static class EngineInfo {
1313        /**
1314         * Engine package name..
1315         */
1316        public String name;
1317        /**
1318         * Localized label for the engine.
1319         */
1320        public String label;
1321        /**
1322         * Icon for the engine.
1323         */
1324        public int icon;
1325        /**
1326         * Whether this engine is a part of the system
1327         * image.
1328         *
1329         * @hide
1330         */
1331        public boolean system;
1332        /**
1333         * The priority the engine declares for the the intent filter
1334         * {@code android.intent.action.TTS_SERVICE}
1335         *
1336         * @hide
1337         */
1338        public int priority;
1339
1340        @Override
1341        public String toString() {
1342            return "EngineInfo{name=" + name + "}";
1343        }
1344
1345    }
1346
1347}
1348