TextToSpeech.java revision d0b927948ec06ab90da639fe0a7bf1ec862301a0
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.
565     */
566    public TextToSpeech(Context context, OnInitListener listener) {
567        this(context, listener, null);
568    }
569
570    /**
571     * The constructor for the TextToSpeech class, using the given TTS engine.
572     * This will also initialize the associated TextToSpeech engine if it isn't already running.
573     *
574     * @param context
575     *            The context this instance is running in.
576     * @param listener
577     *            The {@link TextToSpeech.OnInitListener} that will be called when the
578     *            TextToSpeech engine has initialized.
579     * @param engine Package name of the TTS engine to use.
580     */
581    public TextToSpeech(Context context, OnInitListener listener, String engine) {
582        this(context, listener, engine, null, true);
583    }
584
585    /**
586     * Used by the framework to instantiate TextToSpeech objects with a supplied
587     * package name, instead of using {@link android.content.Context#getPackageName()}
588     *
589     * @hide
590     */
591    public TextToSpeech(Context context, OnInitListener listener, String engine,
592            String packageName, boolean useFallback) {
593        mContext = context;
594        mInitListener = listener;
595        mRequestedEngine = engine;
596        mUseFallback = useFallback;
597
598        mEarcons = new HashMap<String, Uri>();
599        mUtterances = new HashMap<String, Uri>();
600        mUtteranceProgressListener = null;
601
602        mEnginesHelper = new TtsEngines(mContext);
603        if (packageName != null) {
604            mPackageName = packageName;
605        } else {
606            mPackageName = mContext.getPackageName();
607        }
608        initTts();
609    }
610
611    private <R> R runActionNoReconnect(Action<R> action, R errorResult, String method,
612            boolean onlyEstablishedConnection) {
613        return runAction(action, errorResult, method, false, onlyEstablishedConnection);
614    }
615
616    private <R> R runAction(Action<R> action, R errorResult, String method) {
617        return runAction(action, errorResult, method, true, true);
618    }
619
620    private <R> R runAction(Action<R> action, R errorResult, String method,
621            boolean reconnect, boolean onlyEstablishedConnection) {
622        synchronized (mStartLock) {
623            if (mServiceConnection == null) {
624                Log.w(TAG, method + " failed: not bound to TTS engine");
625                return errorResult;
626            }
627            return mServiceConnection.runAction(action, errorResult, method, reconnect,
628                    onlyEstablishedConnection);
629        }
630    }
631
632    private int initTts() {
633        // Step 1: Try connecting to the engine that was requested.
634        if (mRequestedEngine != null) {
635            if (mEnginesHelper.isEngineInstalled(mRequestedEngine)) {
636                if (connectToEngine(mRequestedEngine)) {
637                    mCurrentEngine = mRequestedEngine;
638                    return SUCCESS;
639                } else if (!mUseFallback) {
640                    mCurrentEngine = null;
641                    dispatchOnInit(ERROR);
642                    return ERROR;
643                }
644            } else if (!mUseFallback) {
645                Log.i(TAG, "Requested engine not installed: " + mRequestedEngine);
646                mCurrentEngine = null;
647                dispatchOnInit(ERROR);
648                return ERROR;
649            }
650        }
651
652        // Step 2: Try connecting to the user's default engine.
653        final String defaultEngine = getDefaultEngine();
654        if (defaultEngine != null && !defaultEngine.equals(mRequestedEngine)) {
655            if (connectToEngine(defaultEngine)) {
656                mCurrentEngine = defaultEngine;
657                return SUCCESS;
658            }
659        }
660
661        // Step 3: Try connecting to the highest ranked engine in the
662        // system.
663        final String highestRanked = mEnginesHelper.getHighestRankedEngineName();
664        if (highestRanked != null && !highestRanked.equals(mRequestedEngine) &&
665                !highestRanked.equals(defaultEngine)) {
666            if (connectToEngine(highestRanked)) {
667                mCurrentEngine = highestRanked;
668                return SUCCESS;
669            }
670        }
671
672        // NOTE: The API currently does not allow the caller to query whether
673        // they are actually connected to any engine. This might fail for various
674        // reasons like if the user disables all her TTS engines.
675
676        mCurrentEngine = null;
677        dispatchOnInit(ERROR);
678        return ERROR;
679    }
680
681    private boolean connectToEngine(String engine) {
682        Connection connection = new Connection();
683        Intent intent = new Intent(Engine.INTENT_ACTION_TTS_SERVICE);
684        intent.setPackage(engine);
685        boolean bound = mContext.bindService(intent, connection, Context.BIND_AUTO_CREATE);
686        if (!bound) {
687            Log.e(TAG, "Failed to bind to " + engine);
688            return false;
689        } else {
690            Log.i(TAG, "Sucessfully bound to " + engine);
691            mConnectingServiceConnection = connection;
692            return true;
693        }
694    }
695
696    private void dispatchOnInit(int result) {
697        synchronized (mStartLock) {
698            if (mInitListener != null) {
699                mInitListener.onInit(result);
700                mInitListener = null;
701            }
702        }
703    }
704
705    private IBinder getCallerIdentity() {
706        return mServiceConnection.getCallerIdentity();
707    }
708
709    /**
710     * Releases the resources used by the TextToSpeech engine.
711     * It is good practice for instance to call this method in the onDestroy() method of an Activity
712     * so the TextToSpeech engine can be cleanly stopped.
713     */
714    public void shutdown() {
715        // Special case, we are asked to shutdown connection that did finalize its connection.
716        synchronized (mStartLock) {
717            if (mConnectingServiceConnection != null) {
718                mContext.unbindService(mConnectingServiceConnection);
719                mConnectingServiceConnection = null;
720                return;
721            }
722        }
723
724        // Post connection case
725        runActionNoReconnect(new Action<Void>() {
726            @Override
727            public Void run(ITextToSpeechService service) throws RemoteException {
728                service.setCallback(getCallerIdentity(), null);
729                service.stop(getCallerIdentity());
730                mServiceConnection.disconnect();
731                // Context#unbindService does not result in a call to
732                // ServiceConnection#onServiceDisconnected. As a result, the
733                // service ends up being destroyed (if there are no other open
734                // connections to it) but the process lives on and the
735                // ServiceConnection continues to refer to the destroyed service.
736                //
737                // This leads to tons of log spam about SynthThread being dead.
738                mServiceConnection = null;
739                mCurrentEngine = null;
740                return null;
741            }
742        }, null, "shutdown", false);
743    }
744
745    /**
746     * Adds a mapping between a string of text and a sound resource in a
747     * package. After a call to this method, subsequent calls to
748     * {@link #speak(String, int, HashMap)} will play the specified sound resource
749     * if it is available, or synthesize the text it is missing.
750     *
751     * @param text
752     *            The string of text. Example: <code>"south_south_east"</code>
753     *
754     * @param packagename
755     *            Pass the packagename of the application that contains the
756     *            resource. If the resource is in your own application (this is
757     *            the most common case), then put the packagename of your
758     *            application here.<br/>
759     *            Example: <b>"com.google.marvin.compass"</b><br/>
760     *            The packagename can be found in the AndroidManifest.xml of
761     *            your application.
762     *            <p>
763     *            <code>&lt;manifest xmlns:android=&quot;...&quot;
764     *      package=&quot;<b>com.google.marvin.compass</b>&quot;&gt;</code>
765     *            </p>
766     *
767     * @param resourceId
768     *            Example: <code>R.raw.south_south_east</code>
769     *
770     * @return Code indicating success or failure. See {@link #ERROR} and {@link #SUCCESS}.
771     */
772    public int addSpeech(String text, String packagename, int resourceId) {
773        synchronized (mStartLock) {
774            mUtterances.put(text, makeResourceUri(packagename, resourceId));
775            return SUCCESS;
776        }
777    }
778
779    /**
780     * Adds a mapping between a string of text and a sound file. Using this, it
781     * is possible to add custom pronounciations for a string of text.
782     * After a call to this method, subsequent calls to {@link #speak(String, int, HashMap)}
783     * will play the specified sound resource if it is available, or synthesize the text it is
784     * missing.
785     *
786     * @param text
787     *            The string of text. Example: <code>"south_south_east"</code>
788     * @param filename
789     *            The full path to the sound file (for example:
790     *            "/sdcard/mysounds/hello.wav")
791     *
792     * @return Code indicating success or failure. See {@link #ERROR} and {@link #SUCCESS}.
793     */
794    public int addSpeech(String text, String filename) {
795        synchronized (mStartLock) {
796            mUtterances.put(text, Uri.parse(filename));
797            return SUCCESS;
798        }
799    }
800
801
802    /**
803     * Adds a mapping between a string of text and a sound resource in a
804     * package. Use this to add custom earcons.
805     *
806     * @see #playEarcon(String, int, HashMap)
807     *
808     * @param earcon The name of the earcon.
809     *            Example: <code>"[tick]"</code><br/>
810     *
811     * @param packagename
812     *            the package name of the application that contains the
813     *            resource. This can for instance be the package name of your own application.
814     *            Example: <b>"com.google.marvin.compass"</b><br/>
815     *            The package name can be found in the AndroidManifest.xml of
816     *            the application containing the resource.
817     *            <p>
818     *            <code>&lt;manifest xmlns:android=&quot;...&quot;
819     *      package=&quot;<b>com.google.marvin.compass</b>&quot;&gt;</code>
820     *            </p>
821     *
822     * @param resourceId
823     *            Example: <code>R.raw.tick_snd</code>
824     *
825     * @return Code indicating success or failure. See {@link #ERROR} and {@link #SUCCESS}.
826     */
827    public int addEarcon(String earcon, String packagename, int resourceId) {
828        synchronized(mStartLock) {
829            mEarcons.put(earcon, makeResourceUri(packagename, resourceId));
830            return SUCCESS;
831        }
832    }
833
834    /**
835     * Adds a mapping between a string of text and a sound file.
836     * Use this to add custom earcons.
837     *
838     * @see #playEarcon(String, int, HashMap)
839     *
840     * @param earcon
841     *            The name of the earcon.
842     *            Example: <code>"[tick]"</code>
843     * @param filename
844     *            The full path to the sound file (for example:
845     *            "/sdcard/mysounds/tick.wav")
846     *
847     * @return Code indicating success or failure. See {@link #ERROR} and {@link #SUCCESS}.
848     */
849    public int addEarcon(String earcon, String filename) {
850        synchronized(mStartLock) {
851            mEarcons.put(earcon, Uri.parse(filename));
852            return SUCCESS;
853        }
854    }
855
856    private Uri makeResourceUri(String packageName, int resourceId) {
857        return new Uri.Builder()
858                .scheme(ContentResolver.SCHEME_ANDROID_RESOURCE)
859                .encodedAuthority(packageName)
860                .appendEncodedPath(String.valueOf(resourceId))
861                .build();
862    }
863
864    /**
865     * Speaks the string using the specified queuing strategy and speech parameters.
866     * This method is asynchronous, i.e. the method just adds the request to the queue of TTS
867     * requests and then returns. The synthesis might not have finished (or even started!) at the
868     * time when this method returns. In order to reliably detect errors during synthesis,
869     * we recommend setting an utterance progress listener (see
870     * {@link #setOnUtteranceProgressListener}) and using the
871     * {@link Engine#KEY_PARAM_UTTERANCE_ID} parameter.
872     *
873     * @param text The string of text to be spoken. No longer than
874     *            {@link #getMaxSpeechInputLength()} characters.
875     * @param queueMode The queuing strategy to use, {@link #QUEUE_ADD} or {@link #QUEUE_FLUSH}.
876     * @param params Parameters for the request. Can be null.
877     *            Supported parameter names:
878     *            {@link Engine#KEY_PARAM_STREAM},
879     *            {@link Engine#KEY_PARAM_UTTERANCE_ID},
880     *            {@link Engine#KEY_PARAM_VOLUME},
881     *            {@link Engine#KEY_PARAM_PAN}.
882     *            Engine specific parameters may be passed in but the parameter keys
883     *            must be prefixed by the name of the engine they are intended for. For example
884     *            the keys "com.svox.pico_foo" and "com.svox.pico:bar" will be passed to the
885     *            engine named "com.svox.pico" if it is being used.
886     *
887     * @return {@link #ERROR} or {@link #SUCCESS} of <b>queuing</b> the speak operation.
888     */
889    public int speak(final String text, final int queueMode, final HashMap<String, String> params) {
890        return runAction(new Action<Integer>() {
891            @Override
892            public Integer run(ITextToSpeechService service) throws RemoteException {
893                Uri utteranceUri = mUtterances.get(text);
894                if (utteranceUri != null) {
895                    return service.playAudio(getCallerIdentity(), utteranceUri, queueMode,
896                            getParams(params));
897                } else {
898                    return service.speak(getCallerIdentity(), text, queueMode, getParams(params));
899                }
900            }
901        }, ERROR, "speak");
902    }
903
904    /**
905     * Plays the earcon using the specified queueing mode and parameters.
906     * The earcon must already have been added with {@link #addEarcon(String, String)} or
907     * {@link #addEarcon(String, String, int)}.
908     * This method is asynchronous, i.e. the method just adds the request to the queue of TTS
909     * requests and then returns. The synthesis might not have finished (or even started!) at the
910     * time when this method returns. In order to reliably detect errors during synthesis,
911     * we recommend setting an utterance progress listener (see
912     * {@link #setOnUtteranceProgressListener}) and using the
913     * {@link Engine#KEY_PARAM_UTTERANCE_ID} parameter.
914     *
915     * @param earcon The earcon that should be played
916     * @param queueMode {@link #QUEUE_ADD} or {@link #QUEUE_FLUSH}.
917     * @param params Parameters for the request. Can be null.
918     *            Supported parameter names:
919     *            {@link Engine#KEY_PARAM_STREAM},
920     *            {@link Engine#KEY_PARAM_UTTERANCE_ID}.
921     *            Engine specific parameters may be passed in but the parameter keys
922     *            must be prefixed by the name of the engine they are intended for. For example
923     *            the keys "com.svox.pico_foo" and "com.svox.pico:bar" will be passed to the
924     *            engine named "com.svox.pico" if it is being used.
925     *
926     * @return {@link #ERROR} or {@link #SUCCESS} of <b>queuing</b> the playEarcon operation.
927     */
928    public int playEarcon(final String earcon, final int queueMode,
929            final HashMap<String, String> params) {
930        return runAction(new Action<Integer>() {
931            @Override
932            public Integer run(ITextToSpeechService service) throws RemoteException {
933                Uri earconUri = mEarcons.get(earcon);
934                if (earconUri == null) {
935                    return ERROR;
936                }
937                return service.playAudio(getCallerIdentity(), earconUri, queueMode,
938                        getParams(params));
939            }
940        }, ERROR, "playEarcon");
941    }
942
943    /**
944     * Plays silence for the specified amount of time using the specified
945     * queue mode.
946     * This method is asynchronous, i.e. the method just adds the request to the queue of TTS
947     * requests and then returns. The synthesis might not have finished (or even started!) at the
948     * time when this method returns. In order to reliably detect errors during synthesis,
949     * we recommend setting an utterance progress listener (see
950     * {@link #setOnUtteranceProgressListener}) and using the
951     * {@link Engine#KEY_PARAM_UTTERANCE_ID} parameter.
952     *
953     * @param durationInMs The duration of the silence.
954     * @param queueMode {@link #QUEUE_ADD} or {@link #QUEUE_FLUSH}.
955     * @param params Parameters for the request. Can be null.
956     *            Supported parameter names:
957     *            {@link Engine#KEY_PARAM_UTTERANCE_ID}.
958     *            Engine specific parameters may be passed in but the parameter keys
959     *            must be prefixed by the name of the engine they are intended for. For example
960     *            the keys "com.svox.pico_foo" and "com.svox.pico:bar" will be passed to the
961     *            engine named "com.svox.pico" if it is being used.
962     *
963     * @return {@link #ERROR} or {@link #SUCCESS} of <b>queuing</b> the playSilence operation.
964     */
965    public int playSilence(final long durationInMs, final int queueMode,
966            final HashMap<String, String> params) {
967        return runAction(new Action<Integer>() {
968            @Override
969            public Integer run(ITextToSpeechService service) throws RemoteException {
970                return service.playSilence(getCallerIdentity(), durationInMs, queueMode,
971                        getParams(params));
972            }
973        }, ERROR, "playSilence");
974    }
975
976    /**
977     * Queries the engine for the set of features it supports for a given locale.
978     * Features can either be framework defined, e.g.
979     * {@link TextToSpeech.Engine#KEY_FEATURE_NETWORK_SYNTHESIS} or engine specific.
980     * Engine specific keys must be prefixed by the name of the engine they
981     * are intended for. These keys can be used as parameters to
982     * {@link TextToSpeech#speak(String, int, java.util.HashMap)} and
983     * {@link TextToSpeech#synthesizeToFile(String, java.util.HashMap, String)}.
984     *
985     * Features are boolean flags, and their values in the synthesis parameters
986     * must be behave as per {@link Boolean#parseBoolean(String)}.
987     *
988     * @param locale The locale to query features for.
989     */
990    public Set<String> getFeatures(final Locale locale) {
991        return runAction(new Action<Set<String>>() {
992            @Override
993            public Set<String> run(ITextToSpeechService service) throws RemoteException {
994                String[] features = service.getFeaturesForLanguage(
995                        locale.getISO3Language(), locale.getISO3Country(), locale.getVariant());
996                if (features != null) {
997                    final Set<String> featureSet = new HashSet<String>();
998                    Collections.addAll(featureSet, features);
999                    return featureSet;
1000                }
1001                return null;
1002            }
1003        }, null, "getFeatures");
1004    }
1005
1006    /**
1007     * Checks whether the TTS engine is busy speaking. Note that a speech item is
1008     * considered complete once it's audio data has been sent to the audio mixer, or
1009     * written to a file. There might be a finite lag between this point, and when
1010     * the audio hardware completes playback.
1011     *
1012     * @return {@code true} if the TTS engine is speaking.
1013     */
1014    public boolean isSpeaking() {
1015        return runAction(new Action<Boolean>() {
1016            @Override
1017            public Boolean run(ITextToSpeechService service) throws RemoteException {
1018                return service.isSpeaking();
1019            }
1020        }, false, "isSpeaking");
1021    }
1022
1023    /**
1024     * Interrupts the current utterance (whether played or rendered to file) and discards other
1025     * utterances in the queue.
1026     *
1027     * @return {@link #ERROR} or {@link #SUCCESS}.
1028     */
1029    public int stop() {
1030        return runAction(new Action<Integer>() {
1031            @Override
1032            public Integer run(ITextToSpeechService service) throws RemoteException {
1033                return service.stop(getCallerIdentity());
1034            }
1035        }, ERROR, "stop");
1036    }
1037
1038    /**
1039     * Sets the speech rate.
1040     *
1041     * This has no effect on any pre-recorded speech.
1042     *
1043     * @param speechRate Speech rate. {@code 1.0} is the normal speech rate,
1044     *            lower values slow down the speech ({@code 0.5} is half the normal speech rate),
1045     *            greater values accelerate it ({@code 2.0} is twice the normal speech rate).
1046     *
1047     * @return {@link #ERROR} or {@link #SUCCESS}.
1048     */
1049    public int setSpeechRate(float speechRate) {
1050        if (speechRate > 0.0f) {
1051            int intRate = (int)(speechRate * 100);
1052            if (intRate > 0) {
1053                synchronized (mStartLock) {
1054                    mParams.putInt(Engine.KEY_PARAM_RATE, intRate);
1055                }
1056                return SUCCESS;
1057            }
1058        }
1059        return ERROR;
1060    }
1061
1062    /**
1063     * Sets the speech pitch for the TextToSpeech engine.
1064     *
1065     * This has no effect on any pre-recorded speech.
1066     *
1067     * @param pitch Speech pitch. {@code 1.0} is the normal pitch,
1068     *            lower values lower the tone of the synthesized voice,
1069     *            greater values increase it.
1070     *
1071     * @return {@link #ERROR} or {@link #SUCCESS}.
1072     */
1073    public int setPitch(float pitch) {
1074        if (pitch > 0.0f) {
1075            int intPitch = (int)(pitch * 100);
1076            if (intPitch > 0) {
1077                synchronized (mStartLock) {
1078                    mParams.putInt(Engine.KEY_PARAM_PITCH, intPitch);
1079                }
1080                return SUCCESS;
1081            }
1082        }
1083        return ERROR;
1084    }
1085
1086    /**
1087     * @return the engine currently in use by this TextToSpeech instance.
1088     * @hide
1089     */
1090    public String getCurrentEngine() {
1091        return mCurrentEngine;
1092    }
1093
1094    /**
1095     * Returns a Locale instance describing the language currently being used as the default
1096     * Text-to-speech language.
1097     *
1098     * @return language, country (if any) and variant (if any) used by the client stored in a
1099     *     Locale instance, or {@code null} on error.
1100     */
1101    public Locale getDefaultLanguage() {
1102        return runAction(new Action<Locale>() {
1103            @Override
1104            public Locale run(ITextToSpeechService service) throws RemoteException {
1105                String[] defaultLanguage = service.getClientDefaultLanguage();
1106
1107                return new Locale(defaultLanguage[0], defaultLanguage[1], defaultLanguage[2]);
1108            }
1109        }, null, "getDefaultLanguage");
1110    }
1111
1112    /**
1113     * Sets the text-to-speech language.
1114     * The TTS engine will try to use the closest match to the specified
1115     * language as represented by the Locale, but there is no guarantee that the exact same Locale
1116     * will be used. Use {@link #isLanguageAvailable(Locale)} to check the level of support
1117     * before choosing the language to use for the next utterances.
1118     *
1119     * @param loc The locale describing the language to be used.
1120     *
1121     * @return Code indicating the support status for the locale. See {@link #LANG_AVAILABLE},
1122     *         {@link #LANG_COUNTRY_AVAILABLE}, {@link #LANG_COUNTRY_VAR_AVAILABLE},
1123     *         {@link #LANG_MISSING_DATA} and {@link #LANG_NOT_SUPPORTED}.
1124     */
1125    public int setLanguage(final Locale loc) {
1126        return runAction(new Action<Integer>() {
1127            @Override
1128            public Integer run(ITextToSpeechService service) throws RemoteException {
1129                if (loc == null) {
1130                    return LANG_NOT_SUPPORTED;
1131                }
1132                String language = null, country = null;
1133                try {
1134                    language = loc.getISO3Language();
1135                } catch (MissingResourceException e) {
1136                    Log.w(TAG, "Couldn't retrieve ISO 639-2/T language code for locale: " + loc, e);
1137                    return LANG_NOT_SUPPORTED;
1138                }
1139
1140                try {
1141                    country = loc.getISO3Country();
1142                } catch (MissingResourceException e) {
1143                    Log.w(TAG, "Couldn't retrieve ISO 3166 country code for locale: " + loc, e);
1144                    return LANG_NOT_SUPPORTED;
1145                }
1146
1147                String variant = loc.getVariant();
1148
1149                // Check if the language, country, variant are available, and cache
1150                // the available parts.
1151                // Note that the language is not actually set here, instead it is cached so it
1152                // will be associated with all upcoming utterances.
1153
1154                int result = service.loadLanguage(getCallerIdentity(), language, country, variant);
1155                if (result >= LANG_AVAILABLE){
1156                    if (result < LANG_COUNTRY_VAR_AVAILABLE) {
1157                        variant = "";
1158                        if (result < LANG_COUNTRY_AVAILABLE) {
1159                            country = "";
1160                        }
1161                    }
1162                    mParams.putString(Engine.KEY_PARAM_LANGUAGE, language);
1163                    mParams.putString(Engine.KEY_PARAM_COUNTRY, country);
1164                    mParams.putString(Engine.KEY_PARAM_VARIANT, variant);
1165                }
1166                return result;
1167            }
1168        }, LANG_NOT_SUPPORTED, "setLanguage");
1169    }
1170
1171    /**
1172     * Returns a Locale instance describing the language currently being used for synthesis
1173     * requests sent to the TextToSpeech engine.
1174     *
1175     * In Android 4.2 and before (API <= 17) this function returns the language that is currently
1176     * being used by the TTS engine. That is the last language set by this or any other
1177     * client by a {@link TextToSpeech#setLanguage} call to the same engine.
1178     *
1179     * In Android versions after 4.2 this function returns the language that is currently being
1180     * used for the synthesis requests sent from this client. That is the last language set
1181     * by a {@link TextToSpeech#setLanguage} call on this instance.
1182     *
1183     * @return language, country (if any) and variant (if any) used by the client stored in a
1184     *     Locale instance, or {@code null} on error.
1185     */
1186    public Locale getLanguage() {
1187        return runAction(new Action<Locale>() {
1188            @Override
1189            public Locale run(ITextToSpeechService service) {
1190                /* No service call, but we're accessing mParams, hence need for
1191                   wrapping it as an Action instance */
1192                String lang = mParams.getString(Engine.KEY_PARAM_LANGUAGE, "");
1193                String country = mParams.getString(Engine.KEY_PARAM_COUNTRY, "");
1194                String variant = mParams.getString(Engine.KEY_PARAM_VARIANT, "");
1195                return new Locale(lang, country, variant);
1196            }
1197        }, null, "getLanguage");
1198    }
1199
1200    /**
1201     * Checks if the specified language as represented by the Locale is available and supported.
1202     *
1203     * @param loc The Locale describing the language to be used.
1204     *
1205     * @return Code indicating the support status for the locale. See {@link #LANG_AVAILABLE},
1206     *         {@link #LANG_COUNTRY_AVAILABLE}, {@link #LANG_COUNTRY_VAR_AVAILABLE},
1207     *         {@link #LANG_MISSING_DATA} and {@link #LANG_NOT_SUPPORTED}.
1208     */
1209    public int isLanguageAvailable(final Locale loc) {
1210        return runAction(new Action<Integer>() {
1211            @Override
1212            public Integer run(ITextToSpeechService service) throws RemoteException {
1213                String language = null, country = null;
1214
1215                try {
1216                    language = loc.getISO3Language();
1217                } catch (MissingResourceException e) {
1218                    Log.w(TAG, "Couldn't retrieve ISO 639-2/T language code for locale: " + loc, e);
1219                    return LANG_NOT_SUPPORTED;
1220                }
1221
1222                try {
1223                    country = loc.getISO3Country();
1224                } catch (MissingResourceException e) {
1225                    Log.w(TAG, "Couldn't retrieve ISO 3166 country code for locale: " + loc, e);
1226                    return LANG_NOT_SUPPORTED;
1227                }
1228
1229                return service.isLanguageAvailable(language, country, loc.getVariant());
1230            }
1231        }, LANG_NOT_SUPPORTED, "isLanguageAvailable");
1232    }
1233
1234    /**
1235     * Synthesizes the given text to a file using the specified parameters.
1236     * This method is asynchronous, i.e. the method just adds the request to the queue of TTS
1237     * requests and then returns. The synthesis might not have finished (or even started!) at the
1238     * time when this method returns. In order to reliably detect errors during synthesis,
1239     * we recommend setting an utterance progress listener (see
1240     * {@link #setOnUtteranceProgressListener}) and using the
1241     * {@link Engine#KEY_PARAM_UTTERANCE_ID} parameter.
1242     *
1243     * @param text The text that should be synthesized. No longer than
1244     *            {@link #getMaxSpeechInputLength()} characters.
1245     * @param params Parameters for the request. Can be null.
1246     *            Supported parameter names:
1247     *            {@link Engine#KEY_PARAM_UTTERANCE_ID}.
1248     *            Engine specific parameters may be passed in but the parameter keys
1249     *            must be prefixed by the name of the engine they are intended for. For example
1250     *            the keys "com.svox.pico_foo" and "com.svox.pico:bar" will be passed to the
1251     *            engine named "com.svox.pico" if it is being used.
1252     * @param filename Absolute file filename to write the generated audio data to.It should be
1253     *            something like "/sdcard/myappsounds/mysound.wav".
1254     *
1255     * @return {@link #ERROR} or {@link #SUCCESS} of <b>queuing</b> the synthesizeToFile operation.
1256     */
1257    public int synthesizeToFile(final String text, final HashMap<String, String> params,
1258            final String filename) {
1259        return runAction(new Action<Integer>() {
1260            @Override
1261            public Integer run(ITextToSpeechService service) throws RemoteException {
1262                ParcelFileDescriptor fileDescriptor;
1263                int returnValue;
1264                try {
1265                    File file = new File(filename);
1266                    if(file.exists() && !file.canWrite()) {
1267                        Log.e(TAG, "Can't write to " + filename);
1268                        return ERROR;
1269                    }
1270                    fileDescriptor = ParcelFileDescriptor.open(file,
1271                            ParcelFileDescriptor.MODE_WRITE_ONLY |
1272                            ParcelFileDescriptor.MODE_CREATE |
1273                            ParcelFileDescriptor.MODE_TRUNCATE);
1274                    returnValue = service.synthesizeToFileDescriptor(getCallerIdentity(), text,
1275                            fileDescriptor, getParams(params));
1276                    fileDescriptor.close();
1277                    return returnValue;
1278                } catch (FileNotFoundException e) {
1279                    Log.e(TAG, "Opening file " + filename + " failed", e);
1280                    return ERROR;
1281                } catch (IOException e) {
1282                    Log.e(TAG, "Closing file " + filename + " failed", e);
1283                    return ERROR;
1284                }
1285            }
1286        }, ERROR, "synthesizeToFile");
1287    }
1288
1289    private Bundle getParams(HashMap<String, String> params) {
1290        if (params != null && !params.isEmpty()) {
1291            Bundle bundle = new Bundle(mParams);
1292            copyIntParam(bundle, params, Engine.KEY_PARAM_STREAM);
1293            copyStringParam(bundle, params, Engine.KEY_PARAM_UTTERANCE_ID);
1294            copyFloatParam(bundle, params, Engine.KEY_PARAM_VOLUME);
1295            copyFloatParam(bundle, params, Engine.KEY_PARAM_PAN);
1296
1297            // Copy feature strings defined by the framework.
1298            copyStringParam(bundle, params, Engine.KEY_FEATURE_NETWORK_SYNTHESIS);
1299            copyStringParam(bundle, params, Engine.KEY_FEATURE_EMBEDDED_SYNTHESIS);
1300
1301            // Copy over all parameters that start with the name of the
1302            // engine that we are currently connected to. The engine is
1303            // free to interpret them as it chooses.
1304            if (!TextUtils.isEmpty(mCurrentEngine)) {
1305                for (Map.Entry<String, String> entry : params.entrySet()) {
1306                    final String key = entry.getKey();
1307                    if (key != null && key.startsWith(mCurrentEngine)) {
1308                        bundle.putString(key, entry.getValue());
1309                    }
1310                }
1311            }
1312
1313            return bundle;
1314        } else {
1315            return mParams;
1316        }
1317    }
1318
1319    private void copyStringParam(Bundle bundle, HashMap<String, String> params, String key) {
1320        String value = params.get(key);
1321        if (value != null) {
1322            bundle.putString(key, value);
1323        }
1324    }
1325
1326    private void copyIntParam(Bundle bundle, HashMap<String, String> params, String key) {
1327        String valueString = params.get(key);
1328        if (!TextUtils.isEmpty(valueString)) {
1329            try {
1330                int value = Integer.parseInt(valueString);
1331                bundle.putInt(key, value);
1332            } catch (NumberFormatException ex) {
1333                // don't set the value in the bundle
1334            }
1335        }
1336    }
1337
1338    private void copyFloatParam(Bundle bundle, HashMap<String, String> params, String key) {
1339        String valueString = params.get(key);
1340        if (!TextUtils.isEmpty(valueString)) {
1341            try {
1342                float value = Float.parseFloat(valueString);
1343                bundle.putFloat(key, value);
1344            } catch (NumberFormatException ex) {
1345                // don't set the value in the bundle
1346            }
1347        }
1348    }
1349
1350    /**
1351     * Sets the listener that will be notified when synthesis of an utterance completes.
1352     *
1353     * @param listener The listener to use.
1354     *
1355     * @return {@link #ERROR} or {@link #SUCCESS}.
1356     *
1357     * @deprecated Use {@link #setOnUtteranceProgressListener(UtteranceProgressListener)}
1358     *        instead.
1359     */
1360    @Deprecated
1361    public int setOnUtteranceCompletedListener(final OnUtteranceCompletedListener listener) {
1362        mUtteranceProgressListener = UtteranceProgressListener.from(listener);
1363        return TextToSpeech.SUCCESS;
1364    }
1365
1366    /**
1367     * Sets the listener that will be notified of various events related to the
1368     * synthesis of a given utterance.
1369     *
1370     * See {@link UtteranceProgressListener} and
1371     * {@link TextToSpeech.Engine#KEY_PARAM_UTTERANCE_ID}.
1372     *
1373     * @param listener the listener to use.
1374     * @return {@link #ERROR} or {@link #SUCCESS}
1375     */
1376    public int setOnUtteranceProgressListener(UtteranceProgressListener listener) {
1377        mUtteranceProgressListener = listener;
1378        return TextToSpeech.SUCCESS;
1379    }
1380
1381    /**
1382     * Sets the TTS engine to use.
1383     *
1384     * @deprecated This doesn't inform callers when the TTS engine has been
1385     *        initialized. {@link #TextToSpeech(Context, OnInitListener, String)}
1386     *        can be used with the appropriate engine name. Also, there is no
1387     *        guarantee that the engine specified will be loaded. If it isn't
1388     *        installed or disabled, the user / system wide defaults will apply.
1389     *
1390     * @param enginePackageName The package name for the synthesis engine (e.g. "com.svox.pico")
1391     *
1392     * @return {@link #ERROR} or {@link #SUCCESS}.
1393     */
1394    @Deprecated
1395    public int setEngineByPackageName(String enginePackageName) {
1396        mRequestedEngine = enginePackageName;
1397        return initTts();
1398    }
1399
1400    /**
1401     * Gets the package name of the default speech synthesis engine.
1402     *
1403     * @return Package name of the TTS engine that the user has chosen
1404     *        as their default.
1405     */
1406    public String getDefaultEngine() {
1407        return mEnginesHelper.getDefaultEngine();
1408    }
1409
1410    /**
1411     * Checks whether the user's settings should override settings requested
1412     * by the calling application. As of the Ice cream sandwich release,
1413     * user settings never forcibly override the app's settings.
1414     */
1415    public boolean areDefaultsEnforced() {
1416        return false;
1417    }
1418
1419    /**
1420     * Gets a list of all installed TTS engines.
1421     *
1422     * @return A list of engine info objects. The list can be empty, but never {@code null}.
1423     */
1424    public List<EngineInfo> getEngines() {
1425        return mEnginesHelper.getEngines();
1426    }
1427
1428    private class Connection implements ServiceConnection {
1429        private ITextToSpeechService mService;
1430
1431        private SetupConnectionAsyncTask mOnSetupConnectionAsyncTask;
1432
1433        private boolean mEstablished;
1434
1435        private final ITextToSpeechCallback.Stub mCallback = new ITextToSpeechCallback.Stub() {
1436            @Override
1437            public void onDone(String utteranceId) {
1438                UtteranceProgressListener listener = mUtteranceProgressListener;
1439                if (listener != null) {
1440                    listener.onDone(utteranceId);
1441                }
1442            }
1443
1444            @Override
1445            public void onError(String utteranceId) {
1446                UtteranceProgressListener listener = mUtteranceProgressListener;
1447                if (listener != null) {
1448                    listener.onError(utteranceId);
1449                }
1450            }
1451
1452            @Override
1453            public void onStart(String utteranceId) {
1454                UtteranceProgressListener listener = mUtteranceProgressListener;
1455                if (listener != null) {
1456                    listener.onStart(utteranceId);
1457                }
1458            }
1459        };
1460
1461        private class SetupConnectionAsyncTask extends AsyncTask<Void, Void, Integer> {
1462            private final ComponentName mName;
1463
1464            public SetupConnectionAsyncTask(ComponentName name) {
1465                mName = name;
1466            }
1467
1468            @Override
1469            protected Integer doInBackground(Void... params) {
1470                synchronized(mStartLock) {
1471                    if (isCancelled()) {
1472                        return null;
1473                    }
1474
1475                    try {
1476                        mService.setCallback(getCallerIdentity(), mCallback);
1477                        String[] defaultLanguage = mService.getClientDefaultLanguage();
1478
1479                        mParams.putString(Engine.KEY_PARAM_LANGUAGE, defaultLanguage[0]);
1480                        mParams.putString(Engine.KEY_PARAM_COUNTRY, defaultLanguage[1]);
1481                        mParams.putString(Engine.KEY_PARAM_VARIANT, defaultLanguage[2]);
1482
1483                        Log.i(TAG, "Set up connection to " + mName);
1484                        return SUCCESS;
1485                    } catch (RemoteException re) {
1486                        Log.e(TAG, "Error connecting to service, setCallback() failed");
1487                        return ERROR;
1488                    }
1489                }
1490            }
1491
1492            @Override
1493            protected void onPostExecute(Integer result) {
1494                synchronized(mStartLock) {
1495                    if (mOnSetupConnectionAsyncTask == this) {
1496                        mOnSetupConnectionAsyncTask = null;
1497                    }
1498                    mEstablished = true;
1499                    dispatchOnInit(result);
1500                }
1501            }
1502        }
1503
1504        @Override
1505        public void onServiceConnected(ComponentName name, IBinder service) {
1506            synchronized(mStartLock) {
1507                mConnectingServiceConnection = null;
1508
1509                Log.i(TAG, "Connected to " + name);
1510
1511                if (mOnSetupConnectionAsyncTask != null) {
1512                    mOnSetupConnectionAsyncTask.cancel(false);
1513                }
1514
1515                mService = ITextToSpeechService.Stub.asInterface(service);
1516                mServiceConnection = Connection.this;
1517
1518                mEstablished = false;
1519                mOnSetupConnectionAsyncTask = new SetupConnectionAsyncTask(name);
1520                mOnSetupConnectionAsyncTask.execute();
1521            }
1522        }
1523
1524        public IBinder getCallerIdentity() {
1525            return mCallback;
1526        }
1527
1528        /**
1529         * Clear connection related fields and cancel mOnServiceConnectedAsyncTask if set.
1530         *
1531         * @return true if we cancel mOnSetupConnectionAsyncTask in progress.
1532         */
1533        private boolean clearServiceConnection() {
1534            synchronized(mStartLock) {
1535                boolean result = false;
1536                if (mOnSetupConnectionAsyncTask != null) {
1537                    result = mOnSetupConnectionAsyncTask.cancel(false);
1538                    mOnSetupConnectionAsyncTask = null;
1539                }
1540
1541                mService = null;
1542                // If this is the active connection, clear it
1543                if (mServiceConnection == this) {
1544                    mServiceConnection = null;
1545                }
1546                return result;
1547            }
1548        }
1549
1550        @Override
1551        public void onServiceDisconnected(ComponentName name) {
1552            Log.i(TAG, "Asked to disconnect from " + name);
1553            if (clearServiceConnection()) {
1554                /* We need to protect against a rare case where engine
1555                 * dies just after successful connection - and we process onServiceDisconnected
1556                 * before OnServiceConnectedAsyncTask.onPostExecute. onServiceDisconnected cancels
1557                 * OnServiceConnectedAsyncTask.onPostExecute and we don't call dispatchOnInit
1558                 * with ERROR as argument.
1559                 */
1560                dispatchOnInit(ERROR);
1561            }
1562        }
1563
1564        public void disconnect() {
1565            mContext.unbindService(this);
1566            clearServiceConnection();
1567        }
1568
1569        public boolean isEstablished() {
1570            return mService != null && mEstablished;
1571        }
1572
1573        public <R> R runAction(Action<R> action, R errorResult, String method,
1574                boolean reconnect, boolean onlyEstablishedConnection) {
1575            synchronized (mStartLock) {
1576                try {
1577                    if (mService == null) {
1578                        Log.w(TAG, method + " failed: not connected to TTS engine");
1579                        return errorResult;
1580                    }
1581                    if (onlyEstablishedConnection && !isEstablished()) {
1582                        Log.w(TAG, method + " failed: TTS engine connection not fully set up");
1583                        return errorResult;
1584                    }
1585                    return action.run(mService);
1586                } catch (RemoteException ex) {
1587                    Log.e(TAG, method + " failed", ex);
1588                    if (reconnect) {
1589                        disconnect();
1590                        initTts();
1591                    }
1592                    return errorResult;
1593                }
1594            }
1595        }
1596    }
1597
1598    private interface Action<R> {
1599        R run(ITextToSpeechService service) throws RemoteException;
1600    }
1601
1602    /**
1603     * Information about an installed text-to-speech engine.
1604     *
1605     * @see TextToSpeech#getEngines
1606     */
1607    public static class EngineInfo {
1608        /**
1609         * Engine package name..
1610         */
1611        public String name;
1612        /**
1613         * Localized label for the engine.
1614         */
1615        public String label;
1616        /**
1617         * Icon for the engine.
1618         */
1619        public int icon;
1620        /**
1621         * Whether this engine is a part of the system
1622         * image.
1623         *
1624         * @hide
1625         */
1626        public boolean system;
1627        /**
1628         * The priority the engine declares for the the intent filter
1629         * {@code android.intent.action.TTS_SERVICE}
1630         *
1631         * @hide
1632         */
1633        public int priority;
1634
1635        @Override
1636        public String toString() {
1637            return "EngineInfo{name=" + name + "}";
1638        }
1639
1640    }
1641
1642    /**
1643     * Limit of length of input string passed to speak and synthesizeToFile.
1644     *
1645     * @see #speak
1646     * @see #synthesizeToFile
1647     */
1648    public static int getMaxSpeechInputLength() {
1649        return 4000;
1650    }
1651}
1652