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