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