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