TextToSpeech.java revision fe6ea01602b47f3a0bfc0903a34fc682eae7fa69
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.content.pm.PackageManager;
26import android.content.pm.ResolveInfo;
27import android.content.pm.ServiceInfo;
28import android.media.AudioManager;
29import android.net.Uri;
30import android.os.Bundle;
31import android.os.IBinder;
32import android.os.RemoteException;
33import android.provider.Settings;
34import android.text.TextUtils;
35import android.util.Log;
36
37import java.util.ArrayList;
38import java.util.Collections;
39import java.util.HashMap;
40import java.util.List;
41import java.util.Locale;
42import java.util.Map;
43
44/**
45 *
46 * Synthesizes speech from text for immediate playback or to create a sound file.
47 * <p>A TextToSpeech instance can only be used to synthesize text once it has completed its
48 * initialization. Implement the {@link TextToSpeech.OnInitListener} to be
49 * notified of the completion of the initialization.<br>
50 * When you are done using the TextToSpeech instance, call the {@link #shutdown()} method
51 * to release the native resources used by the TextToSpeech engine.
52 *
53 */
54public class TextToSpeech {
55
56    private static final String TAG = "TextToSpeech";
57
58    /**
59     * Denotes a successful operation.
60     */
61    public static final int SUCCESS = 0;
62    /**
63     * Denotes a generic operation failure.
64     */
65    public static final int ERROR = -1;
66
67    /**
68     * Queue mode where all entries in the playback queue (media to be played
69     * and text to be synthesized) are dropped and replaced by the new entry.
70     */
71    public static final int QUEUE_FLUSH = 0;
72    /**
73     * Queue mode where the new entry is added at the end of the playback queue.
74     */
75    public static final int QUEUE_ADD = 1;
76
77    /**
78     * Denotes the language is available exactly as specified by the locale.
79     */
80    public static final int LANG_COUNTRY_VAR_AVAILABLE = 2;
81
82    /**
83     * Denotes the language is available for the language and country specified
84     * by the locale, but not the variant.
85     */
86    public static final int LANG_COUNTRY_AVAILABLE = 1;
87
88    /**
89     * Denotes the language is available for the language by the locale,
90     * but not the country and variant.
91     */
92    public static final int LANG_AVAILABLE = 0;
93
94    /**
95     * Denotes the language data is missing.
96     */
97    public static final int LANG_MISSING_DATA = -1;
98
99    /**
100     * Denotes the language is not supported.
101     */
102    public static final int LANG_NOT_SUPPORTED = -2;
103
104    /**
105     * Broadcast Action: The TextToSpeech synthesizer has completed processing
106     * of all the text in the speech queue.
107     */
108    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
109    public static final String ACTION_TTS_QUEUE_PROCESSING_COMPLETED =
110            "android.speech.tts.TTS_QUEUE_PROCESSING_COMPLETED";
111
112    /**
113     * Interface definition of a callback to be invoked indicating the completion of the
114     * TextToSpeech engine initialization.
115     */
116    public interface OnInitListener {
117        /**
118         * Called to signal the completion of the TextToSpeech engine initialization.
119         *
120         * @param status {@link TextToSpeech#SUCCESS} or {@link TextToSpeech#ERROR}.
121         */
122        public void onInit(int status);
123    }
124
125    /**
126     * Listener that will be called when the TTS service has
127     * completed synthesizing an utterance. This is only called if the utterance
128     * has an utterance ID (see {@link TextToSpeech.Engine#KEY_PARAM_UTTERANCE_ID}).
129     */
130    public interface OnUtteranceCompletedListener {
131        /**
132         * Called when an utterance has been synthesized.
133         *
134         * @param utteranceId the identifier of the utterance.
135         */
136        public void onUtteranceCompleted(String utteranceId);
137    }
138
139    /**
140     * Constants and parameter names for controlling text-to-speech.
141     */
142    public class Engine {
143
144        /**
145         * Default speech rate.
146         * @hide
147         */
148        public static final int DEFAULT_RATE = 100;
149
150        /**
151         * Default pitch.
152         * @hide
153         */
154        public static final int DEFAULT_PITCH = 100;
155
156        /**
157         * Default volume.
158         * @hide
159         */
160        public static final float DEFAULT_VOLUME = 1.0f;
161
162        /**
163         * Default pan (centered).
164         * @hide
165         */
166        public static final float DEFAULT_PAN = 0.0f;
167
168        /**
169         * Default value for {@link Settings.Secure#TTS_USE_DEFAULTS}.
170         * @hide
171         */
172        public static final int USE_DEFAULTS = 0; // false
173
174        /**
175         * Package name of the default TTS engine.
176         *
177         * TODO: This should come from a system property
178         *
179         * @hide
180         */
181        public static final String DEFAULT_ENGINE = "com.svox.pico";
182
183        /**
184         * Default audio stream used when playing synthesized speech.
185         */
186        public static final int DEFAULT_STREAM = AudioManager.STREAM_MUSIC;
187
188        /**
189         * Indicates success when checking the installation status of the resources used by the
190         * TextToSpeech engine with the {@link #ACTION_CHECK_TTS_DATA} intent.
191         */
192        public static final int CHECK_VOICE_DATA_PASS = 1;
193
194        /**
195         * Indicates failure when checking the installation status of the resources used by the
196         * TextToSpeech engine with the {@link #ACTION_CHECK_TTS_DATA} intent.
197         */
198        public static final int CHECK_VOICE_DATA_FAIL = 0;
199
200        /**
201         * Indicates erroneous data when checking the installation status of the resources used by
202         * the TextToSpeech engine with the {@link #ACTION_CHECK_TTS_DATA} intent.
203         */
204        public static final int CHECK_VOICE_DATA_BAD_DATA = -1;
205
206        /**
207         * Indicates missing resources when checking the installation status of the resources used
208         * by the TextToSpeech engine with the {@link #ACTION_CHECK_TTS_DATA} intent.
209         */
210        public static final int CHECK_VOICE_DATA_MISSING_DATA = -2;
211
212        /**
213         * Indicates missing storage volume when checking the installation status of the resources
214         * used by the TextToSpeech engine with the {@link #ACTION_CHECK_TTS_DATA} intent.
215         */
216        public static final int CHECK_VOICE_DATA_MISSING_VOLUME = -3;
217
218        /**
219         * Intent for starting a TTS service. Services that handle this intent must
220         * extend {@link TextToSpeechService}. Normal applications should not use this intent
221         * directly, instead they should talk to the TTS service using the the methods in this
222         * class.
223         *
224         * @hide Pending API council approval
225         */
226        @SdkConstant(SdkConstantType.SERVICE_ACTION)
227        public static final String INTENT_ACTION_TTS_SERVICE =
228                "android.intent.action.TTS_SERVICE";
229
230        // intents to ask engine to install data or check its data
231        /**
232         * Activity Action: Triggers the platform TextToSpeech engine to
233         * start the activity that installs the resource files on the device
234         * that are required for TTS to be operational. Since the installation
235         * of the data can be interrupted or declined by the user, the application
236         * shouldn't expect successful installation upon return from that intent,
237         * and if need be, should check installation status with
238         * {@link #ACTION_CHECK_TTS_DATA}.
239         */
240        @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
241        public static final String ACTION_INSTALL_TTS_DATA =
242                "android.speech.tts.engine.INSTALL_TTS_DATA";
243
244        /**
245         * Broadcast Action: broadcast to signal the completion of the installation of
246         * the data files used by the synthesis engine. Success or failure is indicated in the
247         * {@link #EXTRA_TTS_DATA_INSTALLED} extra.
248         */
249        @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
250        public static final String ACTION_TTS_DATA_INSTALLED =
251                "android.speech.tts.engine.TTS_DATA_INSTALLED";
252
253        /**
254         * Activity Action: Starts the activity from the platform TextToSpeech
255         * engine to verify the proper installation and availability of the
256         * resource files on the system. Upon completion, the activity will
257         * return one of the following codes:
258         * {@link #CHECK_VOICE_DATA_PASS},
259         * {@link #CHECK_VOICE_DATA_FAIL},
260         * {@link #CHECK_VOICE_DATA_BAD_DATA},
261         * {@link #CHECK_VOICE_DATA_MISSING_DATA}, or
262         * {@link #CHECK_VOICE_DATA_MISSING_VOLUME}.
263         * <p> Moreover, the data received in the activity result will contain the following
264         * fields:
265         * <ul>
266         *   <li>{@link #EXTRA_VOICE_DATA_ROOT_DIRECTORY} which
267         *       indicates the path to the location of the resource files,</li>
268         *   <li>{@link #EXTRA_VOICE_DATA_FILES} which contains
269         *       the list of all the resource files,</li>
270         *   <li>and {@link #EXTRA_VOICE_DATA_FILES_INFO} which
271         *       contains, for each resource file, the description of the language covered by
272         *       the file in the xxx-YYY format, where xxx is the 3-letter ISO language code,
273         *       and YYY is the 3-letter ISO country code.</li>
274         * </ul>
275         */
276        @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
277        public static final String ACTION_CHECK_TTS_DATA =
278                "android.speech.tts.engine.CHECK_TTS_DATA";
279
280        /**
281         * Activity intent for getting some sample text to use for demonstrating TTS.
282         *
283         * @hide This intent was used by engines written against the old API.
284         * Not sure if it should be exposed.
285         */
286        @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
287        public static final String ACTION_GET_SAMPLE_TEXT =
288                "android.speech.tts.engine.GET_SAMPLE_TEXT";
289
290        // extras for a TTS engine's check data activity
291        /**
292         * Extra information received with the {@link #ACTION_CHECK_TTS_DATA} intent where
293         * the TextToSpeech engine specifies the path to its resources.
294         */
295        public static final String EXTRA_VOICE_DATA_ROOT_DIRECTORY = "dataRoot";
296
297        /**
298         * Extra information received with the {@link #ACTION_CHECK_TTS_DATA} intent where
299         * the TextToSpeech engine specifies the file names of its resources under the
300         * resource path.
301         */
302        public static final String EXTRA_VOICE_DATA_FILES = "dataFiles";
303
304        /**
305         * Extra information received with the {@link #ACTION_CHECK_TTS_DATA} intent where
306         * the TextToSpeech engine specifies the locale associated with each resource file.
307         */
308        public static final String EXTRA_VOICE_DATA_FILES_INFO = "dataFilesInfo";
309
310        /**
311         * Extra information received with the {@link #ACTION_CHECK_TTS_DATA} intent where
312         * the TextToSpeech engine returns an ArrayList<String> of all the available voices.
313         * The format of each voice is: lang-COUNTRY-variant where COUNTRY and variant are
314         * optional (ie, "eng" or "eng-USA" or "eng-USA-FEMALE").
315         */
316        public static final String EXTRA_AVAILABLE_VOICES = "availableVoices";
317
318        /**
319         * Extra information received with the {@link #ACTION_CHECK_TTS_DATA} intent where
320         * the TextToSpeech engine returns an ArrayList<String> of all the unavailable voices.
321         * The format of each voice is: lang-COUNTRY-variant where COUNTRY and variant are
322         * optional (ie, "eng" or "eng-USA" or "eng-USA-FEMALE").
323         */
324        public static final String EXTRA_UNAVAILABLE_VOICES = "unavailableVoices";
325
326        /**
327         * Extra information sent with the {@link #ACTION_CHECK_TTS_DATA} intent where the
328         * caller indicates to the TextToSpeech engine which specific sets of voice data to
329         * check for by sending an ArrayList<String> of the voices that are of interest.
330         * The format of each voice is: lang-COUNTRY-variant where COUNTRY and variant are
331         * optional (ie, "eng" or "eng-USA" or "eng-USA-FEMALE").
332         */
333        public static final String EXTRA_CHECK_VOICE_DATA_FOR = "checkVoiceDataFor";
334
335        // extras for a TTS engine's data installation
336        /**
337         * Extra information received with the {@link #ACTION_TTS_DATA_INSTALLED} intent.
338         * It indicates whether the data files for the synthesis engine were successfully
339         * installed. The installation was initiated with the  {@link #ACTION_INSTALL_TTS_DATA}
340         * intent. The possible values for this extra are
341         * {@link TextToSpeech#SUCCESS} and {@link TextToSpeech#ERROR}.
342         */
343        public static final String EXTRA_TTS_DATA_INSTALLED = "dataInstalled";
344
345        // keys for the parameters passed with speak commands. Hidden keys are used internally
346        // to maintain engine state for each TextToSpeech instance.
347        /**
348         * @hide
349         */
350        public static final String KEY_PARAM_RATE = "rate";
351
352        /**
353         * @hide
354         */
355        public static final String KEY_PARAM_LANGUAGE = "language";
356
357        /**
358         * @hide
359         */
360        public static final String KEY_PARAM_COUNTRY = "country";
361
362        /**
363         * @hide
364         */
365        public static final String KEY_PARAM_VARIANT = "variant";
366
367        /**
368         * @hide
369         */
370        public static final String KEY_PARAM_ENGINE = "engine";
371
372        /**
373         * @hide
374         */
375        public static final String KEY_PARAM_PITCH = "pitch";
376
377        /**
378         * Parameter key to specify the audio stream type to be used when speaking text
379         * or playing back a file. The value should be one of the STREAM_ constants
380         * defined in {@link AudioManager}.
381         *
382         * @see TextToSpeech#speak(String, int, HashMap)
383         * @see TextToSpeech#playEarcon(String, int, HashMap)
384         */
385        public static final String KEY_PARAM_STREAM = "streamType";
386
387        /**
388         * Parameter key to identify an utterance in the
389         * {@link TextToSpeech.OnUtteranceCompletedListener} after text has been
390         * spoken, a file has been played back or a silence duration has elapsed.
391         *
392         * @see TextToSpeech#speak(String, int, HashMap)
393         * @see TextToSpeech#playEarcon(String, int, HashMap)
394         * @see TextToSpeech#synthesizeToFile(String, HashMap, String)
395         */
396        public static final String KEY_PARAM_UTTERANCE_ID = "utteranceId";
397
398        /**
399         * Parameter key to specify the speech volume relative to the current stream type
400         * volume used when speaking text. Volume is specified as a float ranging from 0 to 1
401         * where 0 is silence, and 1 is the maximum volume (the default behavior).
402         *
403         * @see TextToSpeech#speak(String, int, HashMap)
404         * @see TextToSpeech#playEarcon(String, int, HashMap)
405         */
406        public static final String KEY_PARAM_VOLUME = "volume";
407
408        /**
409         * Parameter key to specify how the speech is panned from left to right when speaking text.
410         * Pan is specified as a float ranging from -1 to +1 where -1 maps to a hard-left pan,
411         * 0 to center (the default behavior), and +1 to hard-right.
412         *
413         * @see TextToSpeech#speak(String, int, HashMap)
414         * @see TextToSpeech#playEarcon(String, int, HashMap)
415         */
416        public static final String KEY_PARAM_PAN = "pan";
417
418    }
419
420    private final Context mContext;
421    private Connection mServiceConnection;
422    private OnInitListener mInitListener;
423    private final Object mStartLock = new Object();
424
425    private String mRequestedEngine;
426    private final Map<String, Uri> mEarcons;
427    private final Map<String, Uri> mUtterances;
428    private final Bundle mParams = new Bundle();
429
430    /**
431     * The constructor for the TextToSpeech class.
432     * This will also initialize the associated TextToSpeech engine if it isn't already running.
433     *
434     * @param context
435     *            The context this instance is running in.
436     * @param listener
437     *            The {@link TextToSpeech.OnInitListener} that will be called when the
438     *            TextToSpeech engine has initialized.
439     */
440    public TextToSpeech(Context context, OnInitListener listener) {
441        this(context, listener, null);
442    }
443
444    /**
445     * @hide pending approval
446     */
447    public TextToSpeech(Context context, OnInitListener listener, String engine) {
448        mContext = context;
449        mInitListener = listener;
450        mRequestedEngine = engine;
451
452        mEarcons = new HashMap<String, Uri>();
453        mUtterances = new HashMap<String, Uri>();
454
455        initTts();
456    }
457
458    private String getPackageName() {
459        return mContext.getPackageName();
460    }
461
462    private <R> R runActionNoReconnect(Action<R> action, R errorResult, String method) {
463        return runAction(action, errorResult, method, false);
464    }
465
466    private <R> R runAction(Action<R> action, R errorResult, String method) {
467        return runAction(action, errorResult, method, true);
468    }
469
470    private <R> R runAction(Action<R> action, R errorResult, String method, boolean reconnect) {
471        synchronized (mStartLock) {
472            if (mServiceConnection == null) {
473                Log.w(TAG, method + " failed: not bound to TTS engine");
474                return errorResult;
475            }
476            return mServiceConnection.runAction(action, errorResult, method, reconnect);
477        }
478    }
479
480    private int initTts() {
481        String defaultEngine = getDefaultEngine();
482        String engine = defaultEngine;
483        if (!areDefaultsEnforced() && !TextUtils.isEmpty(mRequestedEngine)
484                && isEngineEnabled(engine)) {
485            engine = mRequestedEngine;
486        }
487
488        // Try requested engine
489        if (connectToEngine(engine)) {
490            return SUCCESS;
491        }
492
493        // Fall back to user's default engine if different from the already tested one
494        if (!engine.equals(defaultEngine)) {
495            if (connectToEngine(defaultEngine)) {
496                return SUCCESS;
497            }
498        }
499
500        // Fall back to the hardcoded default if different from the two above
501        if (!defaultEngine.equals(Engine.DEFAULT_ENGINE)
502                && !engine.equals(Engine.DEFAULT_ENGINE)) {
503            if (connectToEngine(Engine.DEFAULT_ENGINE)) {
504                return SUCCESS;
505            }
506        }
507
508        return ERROR;
509    }
510
511    private boolean connectToEngine(String engine) {
512        Connection connection = new Connection();
513        Intent intent = new Intent(Engine.INTENT_ACTION_TTS_SERVICE);
514        intent.setPackage(engine);
515        boolean bound = mContext.bindService(intent, connection, Context.BIND_AUTO_CREATE);
516        if (!bound) {
517            Log.e(TAG, "Failed to bind to " + engine);
518            dispatchOnInit(ERROR);
519            return false;
520        } else {
521            return true;
522        }
523    }
524
525    private void dispatchOnInit(int result) {
526        synchronized (mStartLock) {
527            if (mInitListener != null) {
528                mInitListener.onInit(result);
529                mInitListener = null;
530            }
531        }
532    }
533
534    /**
535     * Releases the resources used by the TextToSpeech engine.
536     * It is good practice for instance to call this method in the onDestroy() method of an Activity
537     * so the TextToSpeech engine can be cleanly stopped.
538     */
539    public void shutdown() {
540        runActionNoReconnect(new Action<Void>() {
541            @Override
542            public Void run(ITextToSpeechService service) throws RemoteException {
543                service.setCallback(getPackageName(), null);
544                service.stop(getPackageName());
545                mServiceConnection.disconnect();
546                return null;
547            }
548        }, null, "shutdown");
549    }
550
551    /**
552     * Adds a mapping between a string of text and a sound resource in a
553     * package. After a call to this method, subsequent calls to
554     * {@link #speak(String, int, HashMap)} will play the specified sound resource
555     * if it is available, or synthesize the text it is missing.
556     *
557     * @param text
558     *            The string of text. Example: <code>"south_south_east"</code>
559     *
560     * @param packagename
561     *            Pass the packagename of the application that contains the
562     *            resource. If the resource is in your own application (this is
563     *            the most common case), then put the packagename of your
564     *            application here.<br/>
565     *            Example: <b>"com.google.marvin.compass"</b><br/>
566     *            The packagename can be found in the AndroidManifest.xml of
567     *            your application.
568     *            <p>
569     *            <code>&lt;manifest xmlns:android=&quot;...&quot;
570     *      package=&quot;<b>com.google.marvin.compass</b>&quot;&gt;</code>
571     *            </p>
572     *
573     * @param resourceId
574     *            Example: <code>R.raw.south_south_east</code>
575     *
576     * @return Code indicating success or failure. See {@link #ERROR} and {@link #SUCCESS}.
577     */
578    public int addSpeech(String text, String packagename, int resourceId) {
579        synchronized (mStartLock) {
580            mUtterances.put(text, makeResourceUri(packagename, resourceId));
581            return SUCCESS;
582        }
583    }
584
585    /**
586     * Adds a mapping between a string of text and a sound file. Using this, it
587     * is possible to add custom pronounciations for a string of text.
588     * After a call to this method, subsequent calls to {@link #speak(String, int, HashMap)}
589     * will play the specified sound resource if it is available, or synthesize the text it is
590     * missing.
591     *
592     * @param text
593     *            The string of text. Example: <code>"south_south_east"</code>
594     * @param filename
595     *            The full path to the sound file (for example:
596     *            "/sdcard/mysounds/hello.wav")
597     *
598     * @return Code indicating success or failure. See {@link #ERROR} and {@link #SUCCESS}.
599     */
600    public int addSpeech(String text, String filename) {
601        synchronized (mStartLock) {
602            mUtterances.put(text, Uri.parse(filename));
603            return SUCCESS;
604        }
605    }
606
607
608    /**
609     * Adds a mapping between a string of text and a sound resource in a
610     * package. Use this to add custom earcons.
611     *
612     * @see #playEarcon(String, int, HashMap)
613     *
614     * @param earcon The name of the earcon.
615     *            Example: <code>"[tick]"</code><br/>
616     *
617     * @param packagename
618     *            the package name of the application that contains the
619     *            resource. This can for instance be the package name of your own application.
620     *            Example: <b>"com.google.marvin.compass"</b><br/>
621     *            The package name can be found in the AndroidManifest.xml of
622     *            the application containing the resource.
623     *            <p>
624     *            <code>&lt;manifest xmlns:android=&quot;...&quot;
625     *      package=&quot;<b>com.google.marvin.compass</b>&quot;&gt;</code>
626     *            </p>
627     *
628     * @param resourceId
629     *            Example: <code>R.raw.tick_snd</code>
630     *
631     * @return Code indicating success or failure. See {@link #ERROR} and {@link #SUCCESS}.
632     */
633    public int addEarcon(String earcon, String packagename, int resourceId) {
634        synchronized(mStartLock) {
635            mEarcons.put(earcon, makeResourceUri(packagename, resourceId));
636            return SUCCESS;
637        }
638    }
639
640    /**
641     * Adds a mapping between a string of text and a sound file.
642     * Use this to add custom earcons.
643     *
644     * @see #playEarcon(String, int, HashMap)
645     *
646     * @param earcon
647     *            The name of the earcon.
648     *            Example: <code>"[tick]"</code>
649     * @param filename
650     *            The full path to the sound file (for example:
651     *            "/sdcard/mysounds/tick.wav")
652     *
653     * @return Code indicating success or failure. See {@link #ERROR} and {@link #SUCCESS}.
654     */
655    public int addEarcon(String earcon, String filename) {
656        synchronized(mStartLock) {
657            mEarcons.put(earcon, Uri.parse(filename));
658            return SUCCESS;
659        }
660    }
661
662    private Uri makeResourceUri(String packageName, int resourceId) {
663        return new Uri.Builder()
664                .scheme(ContentResolver.SCHEME_ANDROID_RESOURCE)
665                .encodedAuthority(packageName)
666                .appendEncodedPath(String.valueOf(resourceId))
667                .build();
668    }
669
670    /**
671     * Speaks the string using the specified queuing strategy and speech
672     * parameters.
673     *
674     * @param text The string of text to be spoken.
675     * @param queueMode The queuing strategy to use, {@link #QUEUE_ADD} or {@link #QUEUE_FLUSH}.
676     * @param params Parameters for the request. Can be null.
677     *            Supported parameter names:
678     *            {@link Engine#KEY_PARAM_STREAM},
679     *            {@link Engine#KEY_PARAM_UTTERANCE_ID},
680     *            {@link Engine#KEY_PARAM_VOLUME},
681     *            {@link Engine#KEY_PARAM_PAN}.
682     *
683     * @return {@link #ERROR} or {@link #SUCCESS}.
684     */
685    public int speak(final String text, final int queueMode, final HashMap<String, String> params) {
686        return runAction(new Action<Integer>() {
687            @Override
688            public Integer run(ITextToSpeechService service) throws RemoteException {
689                Uri utteranceUri = mUtterances.get(text);
690                if (utteranceUri != null) {
691                    return service.playAudio(getPackageName(), utteranceUri, queueMode,
692                            getParams(params));
693                } else {
694                    return service.speak(getPackageName(), text, queueMode, getParams(params));
695                }
696            }
697        }, ERROR, "speak");
698    }
699
700    /**
701     * Plays the earcon using the specified queueing mode and parameters.
702     * The earcon must already have been added with {@link #addEarcon(String, String)} or
703     * {@link #addEarcon(String, String, int)}.
704     *
705     * @param earcon The earcon that should be played
706     * @param queueMode {@link #QUEUE_ADD} or {@link #QUEUE_FLUSH}.
707     * @param params Parameters for the request. Can be null.
708     *            Supported parameter names:
709     *            {@link Engine#KEY_PARAM_STREAM},
710     *            {@link Engine#KEY_PARAM_UTTERANCE_ID}.
711     *
712     * @return {@link #ERROR} or {@link #SUCCESS}.
713     */
714    public int playEarcon(final String earcon, final int queueMode,
715            final HashMap<String, String> params) {
716        return runAction(new Action<Integer>() {
717            @Override
718            public Integer run(ITextToSpeechService service) throws RemoteException {
719                Uri earconUri = mEarcons.get(earcon);
720                if (earconUri == null) {
721                    return ERROR;
722                }
723                return service.playAudio(getPackageName(), earconUri, queueMode,
724                        getParams(params));
725            }
726        }, ERROR, "playEarcon");
727    }
728
729    /**
730     * Plays silence for the specified amount of time using the specified
731     * queue mode.
732     *
733     * @param durationInMs The duration of the silence.
734     * @param queueMode {@link #QUEUE_ADD} or {@link #QUEUE_FLUSH}.
735     * @param params Parameters for the request. Can be null.
736     *            Supported parameter names:
737     *            {@link Engine#KEY_PARAM_UTTERANCE_ID}.
738     *
739     * @return {@link #ERROR} or {@link #SUCCESS}.
740     */
741    public int playSilence(final long durationInMs, final int queueMode,
742            final HashMap<String, String> params) {
743        return runAction(new Action<Integer>() {
744            @Override
745            public Integer run(ITextToSpeechService service) throws RemoteException {
746                return service.playSilence(getPackageName(), durationInMs, queueMode,
747                        getParams(params));
748            }
749        }, ERROR, "playSilence");
750    }
751
752    /**
753     * Checks whether the TTS engine is busy speaking.
754     *
755     * @return {@code true} if the TTS engine is speaking.
756     */
757    public boolean isSpeaking() {
758        return runAction(new Action<Boolean>() {
759            @Override
760            public Boolean run(ITextToSpeechService service) throws RemoteException {
761                return service.isSpeaking();
762            }
763        }, false, "isSpeaking");
764    }
765
766    /**
767     * Interrupts the current utterance (whether played or rendered to file) and discards other
768     * utterances in the queue.
769     *
770     * @return {@link #ERROR} or {@link #SUCCESS}.
771     */
772    public int stop() {
773        return runAction(new Action<Integer>() {
774            @Override
775            public Integer run(ITextToSpeechService service) throws RemoteException {
776                return service.stop(getPackageName());
777            }
778        }, ERROR, "stop");
779    }
780
781    /**
782     * Sets the speech rate.
783     *
784     * This has no effect on any pre-recorded speech.
785     *
786     * @param speechRate Speech rate. {@code 1.0} is the normal speech rate,
787     *            lower values slow down the speech ({@code 0.5} is half the normal speech rate),
788     *            greater values accelerate it ({@code 2.0} is twice the normal speech rate).
789     *
790     * @return {@link #ERROR} or {@link #SUCCESS}.
791     */
792    public int setSpeechRate(float speechRate) {
793        if (speechRate > 0.0f) {
794            int intRate = (int)(speechRate * 100);
795            if (intRate > 0) {
796                synchronized (mStartLock) {
797                    mParams.putInt(Engine.KEY_PARAM_RATE, intRate);
798                }
799                return SUCCESS;
800            }
801        }
802        return ERROR;
803    }
804
805    /**
806     * Sets the speech pitch for the TextToSpeech engine.
807     *
808     * This has no effect on any pre-recorded speech.
809     *
810     * @param pitch Speech pitch. {@code 1.0} is the normal pitch,
811     *            lower values lower the tone of the synthesized voice,
812     *            greater values increase it.
813     *
814     * @return {@link #ERROR} or {@link #SUCCESS}.
815     */
816    public int setPitch(float pitch) {
817        if (pitch > 0.0f) {
818            int intPitch = (int)(pitch * 100);
819            if (intPitch > 0) {
820                synchronized (mStartLock) {
821                    mParams.putInt(Engine.KEY_PARAM_PITCH, intPitch);
822                }
823                return SUCCESS;
824            }
825        }
826        return ERROR;
827    }
828
829    /**
830     * Sets the text-to-speech language.
831     * The TTS engine will try to use the closest match to the specified
832     * language as represented by the Locale, but there is no guarantee that the exact same Locale
833     * will be used. Use {@link #isLanguageAvailable(Locale)} to check the level of support
834     * before choosing the language to use for the next utterances.
835     *
836     * @param loc The locale describing the language to be used.
837     *
838     * @return Code indicating the support status for the locale. See {@link #LANG_AVAILABLE},
839     *         {@link #LANG_COUNTRY_AVAILABLE}, {@link #LANG_COUNTRY_VAR_AVAILABLE},
840     *         {@link #LANG_MISSING_DATA} and {@link #LANG_NOT_SUPPORTED}.
841     */
842    public int setLanguage(final Locale loc) {
843        return runAction(new Action<Integer>() {
844            @Override
845            public Integer run(ITextToSpeechService service) throws RemoteException {
846                if (loc == null) {
847                    return LANG_NOT_SUPPORTED;
848                }
849                String language = loc.getISO3Language();
850                String country = loc.getISO3Country();
851                String variant = loc.getVariant();
852                // Check if the language, country, variant are available, and cache
853                // the available parts.
854                // Note that the language is not actually set here, instead it is cached so it
855                // will be associated with all upcoming utterances.
856                int result = service.loadLanguage(language, country, variant);
857                if (result >= LANG_AVAILABLE){
858                    if (result < LANG_COUNTRY_VAR_AVAILABLE) {
859                        variant = "";
860                        if (result < LANG_COUNTRY_AVAILABLE) {
861                            country = "";
862                        }
863                    }
864                    mParams.putString(Engine.KEY_PARAM_LANGUAGE, language);
865                    mParams.putString(Engine.KEY_PARAM_COUNTRY, country);
866                    mParams.putString(Engine.KEY_PARAM_VARIANT, variant);
867                }
868                return result;
869            }
870        }, LANG_NOT_SUPPORTED, "setLanguage");
871    }
872
873    /**
874     * Returns a Locale instance describing the language currently being used by the TextToSpeech
875     * engine.
876     *
877     * @return language, country (if any) and variant (if any) used by the engine stored in a Locale
878     *     instance, or {@code null} on error.
879     */
880    public Locale getLanguage() {
881        return runAction(new Action<Locale>() {
882            @Override
883            public Locale run(ITextToSpeechService service) throws RemoteException {
884                String[] locStrings = service.getLanguage();
885                if (locStrings != null && locStrings.length == 3) {
886                    return new Locale(locStrings[0], locStrings[1], locStrings[2]);
887                }
888                return null;
889            }
890        }, null, "getLanguage");
891    }
892
893    /**
894     * Checks if the specified language as represented by the Locale is available and supported.
895     *
896     * @param loc The Locale describing the language to be used.
897     *
898     * @return Code indicating the support status for the locale. See {@link #LANG_AVAILABLE},
899     *         {@link #LANG_COUNTRY_AVAILABLE}, {@link #LANG_COUNTRY_VAR_AVAILABLE},
900     *         {@link #LANG_MISSING_DATA} and {@link #LANG_NOT_SUPPORTED}.
901     */
902    public int isLanguageAvailable(final Locale loc) {
903        return runAction(new Action<Integer>() {
904            @Override
905            public Integer run(ITextToSpeechService service) throws RemoteException {
906                return service.isLanguageAvailable(loc.getISO3Language(),
907                        loc.getISO3Country(), loc.getVariant());
908            }
909        }, LANG_NOT_SUPPORTED, "isLanguageAvailable");
910    }
911
912    /**
913     * Synthesizes the given text to a file using the specified parameters.
914     *
915     * @param text Thetext that should be synthesized
916     * @param params Parameters for the request. Can be null.
917     *            Supported parameter names:
918     *            {@link Engine#KEY_PARAM_UTTERANCE_ID}.
919     * @param filename Absolute file filename to write the generated audio data to.It should be
920     *            something like "/sdcard/myappsounds/mysound.wav".
921     *
922     * @return {@link #ERROR} or {@link #SUCCESS}.
923     */
924    public int synthesizeToFile(final String text, final HashMap<String, String> params,
925            final String filename) {
926        return runAction(new Action<Integer>() {
927            @Override
928            public Integer run(ITextToSpeechService service) throws RemoteException {
929                return service.synthesizeToFile(getPackageName(), text, filename,
930                        getParams(params));
931            }
932        }, ERROR, "synthesizeToFile");
933    }
934
935    private Bundle getParams(HashMap<String, String> params) {
936        if (params != null && !params.isEmpty()) {
937            Bundle bundle = new Bundle(mParams);
938            copyIntParam(bundle, params, Engine.KEY_PARAM_STREAM);
939            copyStringParam(bundle, params, Engine.KEY_PARAM_UTTERANCE_ID);
940            copyFloatParam(bundle, params, Engine.KEY_PARAM_VOLUME);
941            copyFloatParam(bundle, params, Engine.KEY_PARAM_PAN);
942            return bundle;
943        } else {
944            return mParams;
945        }
946    }
947
948    private void copyStringParam(Bundle bundle, HashMap<String, String> params, String key) {
949        String value = params.get(key);
950        if (value != null) {
951            bundle.putString(key, value);
952        }
953    }
954
955    private void copyIntParam(Bundle bundle, HashMap<String, String> params, String key) {
956        String valueString = params.get(key);
957        if (!TextUtils.isEmpty(valueString)) {
958            try {
959                int value = Integer.parseInt(valueString);
960                bundle.putInt(key, value);
961            } catch (NumberFormatException ex) {
962                // don't set the value in the bundle
963            }
964        }
965    }
966
967    private void copyFloatParam(Bundle bundle, HashMap<String, String> params, String key) {
968        String valueString = params.get(key);
969        if (!TextUtils.isEmpty(valueString)) {
970            try {
971                float value = Float.parseFloat(valueString);
972                bundle.putFloat(key, value);
973            } catch (NumberFormatException ex) {
974                // don't set the value in the bundle
975            }
976        }
977    }
978
979    /**
980     * Sets the listener that will be notified when synthesis of an utterance completes.
981     *
982     * @param listener The listener to use.
983     *
984     * @return {@link #ERROR} or {@link #SUCCESS}.
985     */
986    public int setOnUtteranceCompletedListener(final OnUtteranceCompletedListener listener) {
987        return runAction(new Action<Integer>() {
988            @Override
989            public Integer run(ITextToSpeechService service) throws RemoteException {
990                ITextToSpeechCallback.Stub callback = new ITextToSpeechCallback.Stub() {
991                    public void utteranceCompleted(String utteranceId) {
992                        if (listener != null) {
993                            listener.onUtteranceCompleted(utteranceId);
994                        }
995                    }
996                };
997                service.setCallback(getPackageName(), callback);
998                return SUCCESS;
999            }
1000        }, ERROR, "setOnUtteranceCompletedListener");
1001    }
1002
1003    /**
1004     * Sets the TTS engine to use.
1005     *
1006     * @param enginePackageName The package name for the synthesis engine (e.g. "com.svox.pico")
1007     *
1008     * @return {@link #ERROR} or {@link #SUCCESS}.
1009     */
1010    // TODO: add @Deprecated{This method does not tell the caller when the new engine
1011    // has been initialized. You should create a new TextToSpeech object with the new
1012    // engine instead.}
1013    public int setEngineByPackageName(String enginePackageName) {
1014        mRequestedEngine = enginePackageName;
1015        return initTts();
1016    }
1017
1018    /**
1019     * Gets the package name of the default speech synthesis engine.
1020     *
1021     * @return Package name of the TTS engine that the user has chosen as their default.
1022     */
1023    public String getDefaultEngine() {
1024        String engine = Settings.Secure.getString(mContext.getContentResolver(),
1025                Settings.Secure.TTS_DEFAULT_SYNTH);
1026        return engine != null ? engine : Engine.DEFAULT_ENGINE;
1027    }
1028
1029    /**
1030     * Checks whether the user's settings should override settings requested by the calling
1031     * application.
1032     */
1033    public boolean areDefaultsEnforced() {
1034        return Settings.Secure.getInt(mContext.getContentResolver(),
1035                Settings.Secure.TTS_USE_DEFAULTS, Engine.USE_DEFAULTS) == 1;
1036    }
1037
1038    private boolean isEngineEnabled(String engine) {
1039        if (Engine.DEFAULT_ENGINE.equals(engine)) {
1040            return true;
1041        }
1042        for (String enabled : getEnabledEngines()) {
1043            if (engine.equals(enabled)) {
1044                return true;
1045            }
1046        }
1047        return false;
1048    }
1049
1050    private String[] getEnabledEngines() {
1051        String str = Settings.Secure.getString(mContext.getContentResolver(),
1052                Settings.Secure.TTS_ENABLED_PLUGINS);
1053        if (TextUtils.isEmpty(str)) {
1054            return new String[0];
1055        }
1056        return str.split(" ");
1057    }
1058
1059    /**
1060     * Gets a list of all installed TTS engines.
1061     *
1062     * @return A list of engine info objects. The list can be empty, but will never by {@code null}.
1063     *
1064     * @hide Pending approval
1065     */
1066    public List<EngineInfo> getEngines() {
1067        PackageManager pm = mContext.getPackageManager();
1068        Intent intent = new Intent(Engine.INTENT_ACTION_TTS_SERVICE);
1069        List<ResolveInfo> resolveInfos =
1070                pm.queryIntentServices(intent, PackageManager.MATCH_DEFAULT_ONLY);
1071        if (resolveInfos == null) return Collections.emptyList();
1072        List<EngineInfo> engines = new ArrayList<EngineInfo>(resolveInfos.size());
1073        for (ResolveInfo resolveInfo : resolveInfos) {
1074            ServiceInfo service = resolveInfo.serviceInfo;
1075            if (service != null) {
1076                EngineInfo engine = new EngineInfo();
1077                // Using just the package name isn't great, since it disallows having
1078                // multiple engines in the same package, but that's what the existing API does.
1079                engine.name = service.packageName;
1080                CharSequence label = service.loadLabel(pm);
1081                engine.label = TextUtils.isEmpty(label) ? engine.name : label.toString();
1082                engine.icon = service.getIconResource();
1083                engines.add(engine);
1084            }
1085        }
1086        return engines;
1087    }
1088
1089    private class Connection implements ServiceConnection {
1090        private ITextToSpeechService mService;
1091
1092        public void onServiceConnected(ComponentName name, IBinder service) {
1093            Log.i(TAG, "Connected to " + name);
1094            synchronized(mStartLock) {
1095                if (mServiceConnection != null) {
1096                    // Disconnect any previous service connection
1097                    mServiceConnection.disconnect();
1098                }
1099                mServiceConnection = this;
1100                mService = ITextToSpeechService.Stub.asInterface(service);
1101                dispatchOnInit(SUCCESS);
1102            }
1103        }
1104
1105        public void onServiceDisconnected(ComponentName name) {
1106            synchronized(mStartLock) {
1107                mService = null;
1108                // If this is the active connection, clear it
1109                if (mServiceConnection == this) {
1110                    mServiceConnection = null;
1111                }
1112            }
1113        }
1114
1115        public void disconnect() {
1116            mContext.unbindService(this);
1117        }
1118
1119        public <R> R runAction(Action<R> action, R errorResult, String method, boolean reconnect) {
1120            try {
1121                synchronized (mStartLock) {
1122                    if (mService == null) {
1123                        Log.w(TAG, method + " failed: not connected to TTS engine");
1124                        return errorResult;
1125                    }
1126                    return action.run(mService);
1127                }
1128            } catch (RemoteException ex) {
1129                Log.e(TAG, method + " failed", ex);
1130                if (reconnect) {
1131                    disconnect();
1132                    initTts();
1133                }
1134                return errorResult;
1135            }
1136        }
1137    }
1138
1139    private interface Action<R> {
1140        R run(ITextToSpeechService service) throws RemoteException;
1141    }
1142
1143    /**
1144     * Information about an installed text-to-speech engine.
1145     *
1146     * @see TextToSpeech#getEngines
1147     * @hide Pending approval
1148     */
1149    public static class EngineInfo {
1150        /**
1151         * Engine package name..
1152         */
1153        public String name;
1154        /**
1155         * Localized label for the engine.
1156         */
1157        public String label;
1158        /**
1159         * Icon for the engine.
1160         */
1161        public int icon;
1162
1163        @Override
1164        public String toString() {
1165            return "EngineInfo{name=" + name + "}";
1166        }
1167
1168    }
1169}
1170