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