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