Home | History | Annotate | Download | only in tts
      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  */
     16 package android.speech.tts;
     17 
     18 import android.annotation.IntDef;
     19 import android.annotation.Nullable;
     20 import android.annotation.RawRes;
     21 import android.annotation.SdkConstant;
     22 import android.annotation.SdkConstant.SdkConstantType;
     23 import android.content.ComponentName;
     24 import android.content.ContentResolver;
     25 import android.content.Context;
     26 import android.content.Intent;
     27 import android.content.ServiceConnection;
     28 import android.media.AudioAttributes;
     29 import android.media.AudioManager;
     30 import android.net.Uri;
     31 import android.os.AsyncTask;
     32 import android.os.Bundle;
     33 import android.os.IBinder;
     34 import android.os.ParcelFileDescriptor;
     35 import android.os.RemoteException;
     36 import android.provider.Settings;
     37 import android.text.TextUtils;
     38 import android.util.Log;
     39 
     40 import java.io.File;
     41 import java.io.FileNotFoundException;
     42 import java.io.IOException;
     43 import java.lang.annotation.Retention;
     44 import java.lang.annotation.RetentionPolicy;
     45 import java.util.Collections;
     46 import java.util.HashMap;
     47 import java.util.HashSet;
     48 import java.util.List;
     49 import java.util.Locale;
     50 import java.util.Map;
     51 import java.util.MissingResourceException;
     52 import java.util.Set;
     53 
     54 /**
     55  *
     56  * Synthesizes speech from text for immediate playback or to create a sound file.
     57  * <p>A TextToSpeech instance can only be used to synthesize text once it has completed its
     58  * initialization. Implement the {@link TextToSpeech.OnInitListener} to be
     59  * notified of the completion of the initialization.<br>
     60  * When you are done using the TextToSpeech instance, call the {@link #shutdown()} method
     61  * to release the native resources used by the TextToSpeech engine.
     62  */
     63 public class TextToSpeech {
     64 
     65     private static final String TAG = "TextToSpeech";
     66 
     67     /**
     68      * Denotes a successful operation.
     69      */
     70     public static final int SUCCESS = 0;
     71     /**
     72      * Denotes a generic operation failure.
     73      */
     74     public static final int ERROR = -1;
     75 
     76     /**
     77      * Denotes a stop requested by a client. It's used only on the service side of the API,
     78      * client should never expect to see this result code.
     79      */
     80     public static final int STOPPED = -2;
     81 
     82     /** @hide */
     83     @IntDef({ERROR_SYNTHESIS, ERROR_SERVICE, ERROR_OUTPUT, ERROR_NETWORK, ERROR_NETWORK_TIMEOUT,
     84              ERROR_INVALID_REQUEST, ERROR_NOT_INSTALLED_YET})
     85     @Retention(RetentionPolicy.SOURCE)
     86     public @interface Error {}
     87 
     88     /**
     89      * Denotes a failure of a TTS engine to synthesize the given input.
     90      */
     91     public static final int ERROR_SYNTHESIS = -3;
     92 
     93     /**
     94      * Denotes a failure of a TTS service.
     95      */
     96     public static final int ERROR_SERVICE = -4;
     97 
     98     /**
     99      * Denotes a failure related to the output (audio device or a file).
    100      */
    101     public static final int ERROR_OUTPUT = -5;
    102 
    103     /**
    104      * Denotes a failure caused by a network connectivity problems.
    105      */
    106     public static final int ERROR_NETWORK = -6;
    107 
    108     /**
    109      * Denotes a failure caused by network timeout.
    110      */
    111     public static final int ERROR_NETWORK_TIMEOUT = -7;
    112 
    113     /**
    114      * Denotes a failure caused by an invalid request.
    115      */
    116     public static final int ERROR_INVALID_REQUEST = -8;
    117 
    118     /**
    119      * Denotes a failure caused by an unfinished download of the voice data.
    120      * @see Engine#KEY_FEATURE_NOT_INSTALLED
    121      */
    122     public static final int ERROR_NOT_INSTALLED_YET = -9;
    123 
    124     /**
    125      * Queue mode where all entries in the playback queue (media to be played
    126      * and text to be synthesized) are dropped and replaced by the new entry.
    127      * Queues are flushed with respect to a given calling app. Entries in the queue
    128      * from other callees are not discarded.
    129      */
    130     public static final int QUEUE_FLUSH = 0;
    131     /**
    132      * Queue mode where the new entry is added at the end of the playback queue.
    133      */
    134     public static final int QUEUE_ADD = 1;
    135     /**
    136      * Queue mode where the entire playback queue is purged. This is different
    137      * from {@link #QUEUE_FLUSH} in that all entries are purged, not just entries
    138      * from a given caller.
    139      *
    140      * @hide
    141      */
    142     static final int QUEUE_DESTROY = 2;
    143 
    144     /**
    145      * Denotes the language is available exactly as specified by the locale.
    146      */
    147     public static final int LANG_COUNTRY_VAR_AVAILABLE = 2;
    148 
    149     /**
    150      * Denotes the language is available for the language and country specified
    151      * by the locale, but not the variant.
    152      */
    153     public static final int LANG_COUNTRY_AVAILABLE = 1;
    154 
    155     /**
    156      * Denotes the language is available for the language by the locale,
    157      * but not the country and variant.
    158      */
    159     public static final int LANG_AVAILABLE = 0;
    160 
    161     /**
    162      * Denotes the language data is missing.
    163      */
    164     public static final int LANG_MISSING_DATA = -1;
    165 
    166     /**
    167      * Denotes the language is not supported.
    168      */
    169     public static final int LANG_NOT_SUPPORTED = -2;
    170 
    171     /**
    172      * Broadcast Action: The TextToSpeech synthesizer has completed processing
    173      * of all the text in the speech queue.
    174      *
    175      * Note that this notifies callers when the <b>engine</b> has finished has
    176      * processing text data. Audio playback might not have completed (or even started)
    177      * at this point. If you wish to be notified when this happens, see
    178      * {@link OnUtteranceCompletedListener}.
    179      */
    180     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
    181     public static final String ACTION_TTS_QUEUE_PROCESSING_COMPLETED =
    182             "android.speech.tts.TTS_QUEUE_PROCESSING_COMPLETED";
    183 
    184     /**
    185      * Interface definition of a callback to be invoked indicating the completion of the
    186      * TextToSpeech engine initialization.
    187      */
    188     public interface OnInitListener {
    189         /**
    190          * Called to signal the completion of the TextToSpeech engine initialization.
    191          *
    192          * @param status {@link TextToSpeech#SUCCESS} or {@link TextToSpeech#ERROR}.
    193          */
    194         void onInit(int status);
    195     }
    196 
    197     /**
    198      * Listener that will be called when the TTS service has
    199      * completed synthesizing an utterance. This is only called if the utterance
    200      * has an utterance ID (see {@link TextToSpeech.Engine#KEY_PARAM_UTTERANCE_ID}).
    201      *
    202      * @deprecated Use {@link UtteranceProgressListener} instead.
    203      */
    204     @Deprecated
    205     public interface OnUtteranceCompletedListener {
    206         /**
    207          * Called when an utterance has been synthesized.
    208          *
    209          * @param utteranceId the identifier of the utterance.
    210          */
    211         void onUtteranceCompleted(String utteranceId);
    212     }
    213 
    214     /**
    215      * Constants and parameter names for controlling text-to-speech. These include:
    216      *
    217      * <ul>
    218      *     <li>
    219      *         Intents to ask engine to install data or check its data and
    220      *         extras for a TTS engine's check data activity.
    221      *     </li>
    222      *     <li>
    223      *         Keys for the parameters passed with speak commands, e.g.
    224      *         {@link Engine#KEY_PARAM_UTTERANCE_ID}, {@link Engine#KEY_PARAM_STREAM}.
    225      *     </li>
    226      *     <li>
    227      *         A list of feature strings that engines might support, e.g
    228      *         {@link Engine#KEY_FEATURE_NETWORK_SYNTHESIS}. These values may be passed in to
    229      *         {@link TextToSpeech#speak} and {@link TextToSpeech#synthesizeToFile} to modify
    230      *         engine behaviour. The engine can be queried for the set of features it supports
    231      *         through {@link TextToSpeech#getFeatures(java.util.Locale)}.
    232      *     </li>
    233      * </ul>
    234      */
    235     public class Engine {
    236 
    237         /**
    238          * Default speech rate.
    239          * @hide
    240          */
    241         public static final int DEFAULT_RATE = 100;
    242 
    243         /**
    244          * Default pitch.
    245          * @hide
    246          */
    247         public static final int DEFAULT_PITCH = 100;
    248 
    249         /**
    250          * Default volume.
    251          * @hide
    252          */
    253         public static final float DEFAULT_VOLUME = 1.0f;
    254 
    255         /**
    256          * Default pan (centered).
    257          * @hide
    258          */
    259         public static final float DEFAULT_PAN = 0.0f;
    260 
    261         /**
    262          * Default value for {@link Settings.Secure#TTS_USE_DEFAULTS}.
    263          * @hide
    264          */
    265         public static final int USE_DEFAULTS = 0; // false
    266 
    267         /**
    268          * Package name of the default TTS engine.
    269          *
    270          * @hide
    271          * @deprecated No longer in use, the default engine is determined by
    272          *         the sort order defined in {@link TtsEngines}. Note that
    273          *         this doesn't "break" anything because there is no guarantee that
    274          *         the engine specified below is installed on a given build, let
    275          *         alone be the default.
    276          */
    277         @Deprecated
    278         public static final String DEFAULT_ENGINE = "com.svox.pico";
    279 
    280         /**
    281          * Default audio stream used when playing synthesized speech.
    282          */
    283         public static final int DEFAULT_STREAM = AudioManager.STREAM_MUSIC;
    284 
    285         /**
    286          * Indicates success when checking the installation status of the resources used by the
    287          * TextToSpeech engine with the {@link #ACTION_CHECK_TTS_DATA} intent.
    288          */
    289         public static final int CHECK_VOICE_DATA_PASS = 1;
    290 
    291         /**
    292          * Indicates failure when checking the installation status of the resources used by the
    293          * TextToSpeech engine with the {@link #ACTION_CHECK_TTS_DATA} intent.
    294          */
    295         public static final int CHECK_VOICE_DATA_FAIL = 0;
    296 
    297         /**
    298          * Indicates erroneous data when checking the installation status of the resources used by
    299          * the TextToSpeech engine with the {@link #ACTION_CHECK_TTS_DATA} intent.
    300          *
    301          * @deprecated Use CHECK_VOICE_DATA_FAIL instead.
    302          */
    303         @Deprecated
    304         public static final int CHECK_VOICE_DATA_BAD_DATA = -1;
    305 
    306         /**
    307          * Indicates missing resources when checking the installation status of the resources used
    308          * by the TextToSpeech engine with the {@link #ACTION_CHECK_TTS_DATA} intent.
    309          *
    310          * @deprecated Use CHECK_VOICE_DATA_FAIL instead.
    311          */
    312         @Deprecated
    313         public static final int CHECK_VOICE_DATA_MISSING_DATA = -2;
    314 
    315         /**
    316          * Indicates missing storage volume when checking the installation status of the resources
    317          * used by the TextToSpeech engine with the {@link #ACTION_CHECK_TTS_DATA} intent.
    318          *
    319          * @deprecated Use CHECK_VOICE_DATA_FAIL instead.
    320          */
    321         @Deprecated
    322         public static final int CHECK_VOICE_DATA_MISSING_VOLUME = -3;
    323 
    324         /**
    325          * Intent for starting a TTS service. Services that handle this intent must
    326          * extend {@link TextToSpeechService}. Normal applications should not use this intent
    327          * directly, instead they should talk to the TTS service using the the methods in this
    328          * class.
    329          */
    330         @SdkConstant(SdkConstantType.SERVICE_ACTION)
    331         public static final String INTENT_ACTION_TTS_SERVICE =
    332                 "android.intent.action.TTS_SERVICE";
    333 
    334         /**
    335          * Name under which a text to speech engine publishes information about itself.
    336          * This meta-data should reference an XML resource containing a
    337          * <code>&lt;{@link android.R.styleable#TextToSpeechEngine tts-engine}&gt;</code>
    338          * tag.
    339          */
    340         public static final String SERVICE_META_DATA = "android.speech.tts";
    341 
    342         // intents to ask engine to install data or check its data
    343         /**
    344          * Activity Action: Triggers the platform TextToSpeech engine to
    345          * start the activity that installs the resource files on the device
    346          * that are required for TTS to be operational. Since the installation
    347          * of the data can be interrupted or declined by the user, the application
    348          * shouldn't expect successful installation upon return from that intent,
    349          * and if need be, should check installation status with
    350          * {@link #ACTION_CHECK_TTS_DATA}.
    351          */
    352         @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
    353         public static final String ACTION_INSTALL_TTS_DATA =
    354                 "android.speech.tts.engine.INSTALL_TTS_DATA";
    355 
    356         /**
    357          * Broadcast Action: broadcast to signal the change in the list of available
    358          * languages or/and their features.
    359          */
    360         @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
    361         public static final String ACTION_TTS_DATA_INSTALLED =
    362                 "android.speech.tts.engine.TTS_DATA_INSTALLED";
    363 
    364         /**
    365          * Activity Action: Starts the activity from the platform TextToSpeech
    366          * engine to verify the proper installation and availability of the
    367          * resource files on the system. Upon completion, the activity will
    368          * return one of the following codes:
    369          * {@link #CHECK_VOICE_DATA_PASS},
    370          * {@link #CHECK_VOICE_DATA_FAIL},
    371          * <p> Moreover, the data received in the activity result will contain the following
    372          * fields:
    373          * <ul>
    374          *   <li>{@link #EXTRA_AVAILABLE_VOICES} which contains an ArrayList<String> of all the
    375          *   available voices. The format of each voice is: lang-COUNTRY-variant where COUNTRY and
    376          *   variant are optional (ie, "eng" or "eng-USA" or "eng-USA-FEMALE").</li>
    377          *   <li>{@link #EXTRA_UNAVAILABLE_VOICES} which contains an ArrayList<String> of all the
    378          *   unavailable voices (ones that user can install). The format of each voice is:
    379          *   lang-COUNTRY-variant where COUNTRY and variant are optional (ie, "eng" or
    380          *   "eng-USA" or "eng-USA-FEMALE").</li>
    381          * </ul>
    382          */
    383         @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
    384         public static final String ACTION_CHECK_TTS_DATA =
    385                 "android.speech.tts.engine.CHECK_TTS_DATA";
    386 
    387         /**
    388          * Activity intent for getting some sample text to use for demonstrating TTS. Specific
    389          * locale have to be requested by passing following extra parameters:
    390          * <ul>
    391          *   <li>language</li>
    392          *   <li>country</li>
    393          *   <li>variant</li>
    394          * </ul>
    395          *
    396          * Upon completion, the activity result may contain the following fields:
    397          * <ul>
    398          *   <li>{@link #EXTRA_SAMPLE_TEXT} which contains an String with sample text.</li>
    399          * </ul>
    400          */
    401         @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
    402         public static final String ACTION_GET_SAMPLE_TEXT =
    403                 "android.speech.tts.engine.GET_SAMPLE_TEXT";
    404 
    405         /**
    406          * Extra information received with the {@link #ACTION_GET_SAMPLE_TEXT} intent result where
    407          * the TextToSpeech engine returns an String with sample text for requested voice
    408          */
    409         public static final String EXTRA_SAMPLE_TEXT = "sampleText";
    410 
    411 
    412         // extras for a TTS engine's check data activity
    413         /**
    414          * Extra information received with the {@link #ACTION_CHECK_TTS_DATA} intent result where
    415          * the TextToSpeech engine returns an ArrayList<String> of all the available voices.
    416          * The format of each voice is: lang-COUNTRY-variant where COUNTRY and variant are
    417          * optional (ie, "eng" or "eng-USA" or "eng-USA-FEMALE").
    418          */
    419         public static final String EXTRA_AVAILABLE_VOICES = "availableVoices";
    420 
    421         /**
    422          * Extra information received with the {@link #ACTION_CHECK_TTS_DATA} intent result where
    423          * the TextToSpeech engine returns an ArrayList<String> of all the unavailable voices.
    424          * The format of each voice is: lang-COUNTRY-variant where COUNTRY and variant are
    425          * optional (ie, "eng" or "eng-USA" or "eng-USA-FEMALE").
    426          */
    427         public static final String EXTRA_UNAVAILABLE_VOICES = "unavailableVoices";
    428 
    429         /**
    430          * Extra information received with the {@link #ACTION_CHECK_TTS_DATA} intent result where
    431          * the TextToSpeech engine specifies the path to its resources.
    432          *
    433          * It may be used by language packages to find out where to put their data.
    434          *
    435          * @deprecated TTS engine implementation detail, this information has no use for
    436          * text-to-speech API client.
    437          */
    438         @Deprecated
    439         public static final String EXTRA_VOICE_DATA_ROOT_DIRECTORY = "dataRoot";
    440 
    441         /**
    442          * Extra information received with the {@link #ACTION_CHECK_TTS_DATA} intent result where
    443          * the TextToSpeech engine specifies the file names of its resources under the
    444          * resource path.
    445          *
    446          * @deprecated TTS engine implementation detail, this information has no use for
    447          * text-to-speech API client.
    448          */
    449         @Deprecated
    450         public static final String EXTRA_VOICE_DATA_FILES = "dataFiles";
    451 
    452         /**
    453          * Extra information received with the {@link #ACTION_CHECK_TTS_DATA} intent result where
    454          * the TextToSpeech engine specifies the locale associated with each resource file.
    455          *
    456          * @deprecated TTS engine implementation detail, this information has no use for
    457          * text-to-speech API client.
    458          */
    459         @Deprecated
    460         public static final String EXTRA_VOICE_DATA_FILES_INFO = "dataFilesInfo";
    461 
    462         /**
    463          * Extra information sent with the {@link #ACTION_CHECK_TTS_DATA} intent where the
    464          * caller indicates to the TextToSpeech engine which specific sets of voice data to
    465          * check for by sending an ArrayList<String> of the voices that are of interest.
    466          * The format of each voice is: lang-COUNTRY-variant where COUNTRY and variant are
    467          * optional (ie, "eng" or "eng-USA" or "eng-USA-FEMALE").
    468          *
    469          * @deprecated Redundant functionality, checking for existence of specific sets of voice
    470          * data can be done on client side.
    471          */
    472         @Deprecated
    473         public static final String EXTRA_CHECK_VOICE_DATA_FOR = "checkVoiceDataFor";
    474 
    475         // extras for a TTS engine's data installation
    476         /**
    477          * Extra information received with the {@link #ACTION_TTS_DATA_INSTALLED} intent result.
    478          * It indicates whether the data files for the synthesis engine were successfully
    479          * installed. The installation was initiated with the  {@link #ACTION_INSTALL_TTS_DATA}
    480          * intent. The possible values for this extra are
    481          * {@link TextToSpeech#SUCCESS} and {@link TextToSpeech#ERROR}.
    482          *
    483          * @deprecated No longer in use. If client ise interested in information about what
    484          * changed, is should send ACTION_CHECK_TTS_DATA intent to discover available voices.
    485          */
    486         @Deprecated
    487         public static final String EXTRA_TTS_DATA_INSTALLED = "dataInstalled";
    488 
    489         // keys for the parameters passed with speak commands. Hidden keys are used internally
    490         // to maintain engine state for each TextToSpeech instance.
    491         /**
    492          * @hide
    493          */
    494         public static final String KEY_PARAM_RATE = "rate";
    495 
    496         /**
    497          * @hide
    498          */
    499         public static final String KEY_PARAM_VOICE_NAME = "voiceName";
    500 
    501         /**
    502          * @hide
    503          */
    504         public static final String KEY_PARAM_LANGUAGE = "language";
    505 
    506         /**
    507          * @hide
    508          */
    509         public static final String KEY_PARAM_COUNTRY = "country";
    510 
    511         /**
    512          * @hide
    513          */
    514         public static final String KEY_PARAM_VARIANT = "variant";
    515 
    516         /**
    517          * @hide
    518          */
    519         public static final String KEY_PARAM_ENGINE = "engine";
    520 
    521         /**
    522          * @hide
    523          */
    524         public static final String KEY_PARAM_PITCH = "pitch";
    525 
    526         /**
    527          * Parameter key to specify the audio stream type to be used when speaking text
    528          * or playing back a file. The value should be one of the STREAM_ constants
    529          * defined in {@link AudioManager}.
    530          *
    531          * @see TextToSpeech#speak(String, int, HashMap)
    532          * @see TextToSpeech#playEarcon(String, int, HashMap)
    533          */
    534         public static final String KEY_PARAM_STREAM = "streamType";
    535 
    536         /**
    537          * Parameter key to specify the audio attributes to be used when
    538          * speaking text or playing back a file. The value should be set
    539          * using {@link TextToSpeech#setAudioAttributes(AudioAttributes)}.
    540          *
    541          * @see TextToSpeech#speak(String, int, HashMap)
    542          * @see TextToSpeech#playEarcon(String, int, HashMap)
    543          * @hide
    544          */
    545         public static final String KEY_PARAM_AUDIO_ATTRIBUTES = "audioAttributes";
    546 
    547         /**
    548          * Parameter key to identify an utterance in the
    549          * {@link TextToSpeech.OnUtteranceCompletedListener} after text has been
    550          * spoken, a file has been played back or a silence duration has elapsed.
    551          *
    552          * @see TextToSpeech#speak(String, int, HashMap)
    553          * @see TextToSpeech#playEarcon(String, int, HashMap)
    554          * @see TextToSpeech#synthesizeToFile(String, HashMap, String)
    555          */
    556         public static final String KEY_PARAM_UTTERANCE_ID = "utteranceId";
    557 
    558         /**
    559          * Parameter key to specify the speech volume relative to the current stream type
    560          * volume used when speaking text. Volume is specified as a float ranging from 0 to 1
    561          * where 0 is silence, and 1 is the maximum volume (the default behavior).
    562          *
    563          * @see TextToSpeech#speak(String, int, HashMap)
    564          * @see TextToSpeech#playEarcon(String, int, HashMap)
    565          */
    566         public static final String KEY_PARAM_VOLUME = "volume";
    567 
    568         /**
    569          * Parameter key to specify how the speech is panned from left to right when speaking text.
    570          * Pan is specified as a float ranging from -1 to +1 where -1 maps to a hard-left pan,
    571          * 0 to center (the default behavior), and +1 to hard-right.
    572          *
    573          * @see TextToSpeech#speak(String, int, HashMap)
    574          * @see TextToSpeech#playEarcon(String, int, HashMap)
    575          */
    576         public static final String KEY_PARAM_PAN = "pan";
    577 
    578         /**
    579          * Feature key for network synthesis. See {@link TextToSpeech#getFeatures(Locale)}
    580          * for a description of how feature keys work. If set (and supported by the engine
    581          * as per {@link TextToSpeech#getFeatures(Locale)}, the engine must
    582          * use network based synthesis.
    583          *
    584          * @see TextToSpeech#speak(String, int, java.util.HashMap)
    585          * @see TextToSpeech#synthesizeToFile(String, java.util.HashMap, String)
    586          * @see TextToSpeech#getFeatures(java.util.Locale)
    587          *
    588          * @deprecated Starting from API level 21, to select network synthesis, call
    589          * {@link TextToSpeech#getVoices()}, find a suitable network voice
    590          * ({@link Voice#isNetworkConnectionRequired()}) and pass it
    591          * to {@link TextToSpeech#setVoice(Voice)}.
    592          */
    593         @Deprecated
    594         public static final String KEY_FEATURE_NETWORK_SYNTHESIS = "networkTts";
    595 
    596         /**
    597          * Feature key for embedded synthesis. See {@link TextToSpeech#getFeatures(Locale)}
    598          * for a description of how feature keys work. If set and supported by the engine
    599          * as per {@link TextToSpeech#getFeatures(Locale)}, the engine must synthesize
    600          * text on-device (without making network requests).
    601          *
    602          * @see TextToSpeech#speak(String, int, java.util.HashMap)
    603          * @see TextToSpeech#synthesizeToFile(String, java.util.HashMap, String)
    604          * @see TextToSpeech#getFeatures(java.util.Locale)
    605 
    606          * @deprecated Starting from API level 21, to select embedded synthesis, call
    607          * ({@link TextToSpeech#getVoices()}, find a suitable embedded voice
    608          * ({@link Voice#isNetworkConnectionRequired()}) and pass it
    609          * to {@link TextToSpeech#setVoice(Voice)}).
    610          */
    611         @Deprecated
    612         public static final String KEY_FEATURE_EMBEDDED_SYNTHESIS = "embeddedTts";
    613 
    614         /**
    615          * Parameter key to specify an audio session identifier (obtained from
    616          * {@link AudioManager#generateAudioSessionId()}) that will be used by the request audio
    617          * output. It can be used to associate one of the {@link android.media.audiofx.AudioEffect}
    618          * objects with the synthesis (or earcon) output.
    619          *
    620          * @see TextToSpeech#speak(String, int, HashMap)
    621          * @see TextToSpeech#playEarcon(String, int, HashMap)
    622          */
    623         public static final String KEY_PARAM_SESSION_ID = "sessionId";
    624 
    625         /**
    626          * Feature key that indicates that the voice may need to download additional data to be fully
    627          * functional. The download will be triggered by calling
    628          * {@link TextToSpeech#setVoice(Voice)} or {@link TextToSpeech#setLanguage(Locale)}.
    629          * Until download is complete, each synthesis request will either report
    630          * {@link TextToSpeech#ERROR_NOT_INSTALLED_YET} error, or use a different voice to synthesize
    631          * the request. This feature should NOT be used as a key of a request parameter.
    632          *
    633          * @see TextToSpeech#getFeatures(java.util.Locale)
    634          * @see Voice#getFeatures()
    635          */
    636         public static final String KEY_FEATURE_NOT_INSTALLED = "notInstalled";
    637 
    638         /**
    639          * Feature key that indicate that a network timeout can be set for the request. If set and
    640          * supported as per {@link TextToSpeech#getFeatures(Locale)} or {@link Voice#getFeatures()},
    641          * it can be used as request parameter to set the maximum allowed time for a single
    642          * request attempt, in milliseconds, before synthesis fails. When used as a key of
    643          * a request parameter, its value should be a string with an integer value.
    644          *
    645          * @see TextToSpeech#getFeatures(java.util.Locale)
    646          * @see Voice#getFeatures()
    647          */
    648         public static final String KEY_FEATURE_NETWORK_TIMEOUT_MS = "networkTimeoutMs";
    649 
    650         /**
    651          * Feature key that indicates that network request retries count can be set for the request.
    652          * If set and supported as per {@link TextToSpeech#getFeatures(Locale)} or
    653          * {@link Voice#getFeatures()}, it can be used as a request parameter to set the
    654          * number of network request retries that are attempted in case of failure. When used as
    655          * a key of a request parameter, its value should be a string with an integer value.
    656          *
    657          * @see TextToSpeech#getFeatures(java.util.Locale)
    658          * @see Voice#getFeatures()
    659          */
    660         public static final String KEY_FEATURE_NETWORK_RETRIES_COUNT = "networkRetriesCount";
    661     }
    662 
    663     private final Context mContext;
    664     private Connection mConnectingServiceConnection;
    665     private Connection mServiceConnection;
    666     private OnInitListener mInitListener;
    667     // Written from an unspecified application thread, read from
    668     // a binder thread.
    669     @Nullable private volatile UtteranceProgressListener mUtteranceProgressListener;
    670     private final Object mStartLock = new Object();
    671 
    672     private String mRequestedEngine;
    673     // Whether to initialize this TTS object with the default engine,
    674     // if the requested engine is not available. Valid only if mRequestedEngine
    675     // is not null. Used only for testing, though potentially useful API wise
    676     // too.
    677     private final boolean mUseFallback;
    678     private final Map<String, Uri> mEarcons;
    679     private final Map<CharSequence, Uri> mUtterances;
    680     private final Bundle mParams = new Bundle();
    681     private final TtsEngines mEnginesHelper;
    682     private volatile String mCurrentEngine = null;
    683 
    684     /**
    685      * The constructor for the TextToSpeech class, using the default TTS engine.
    686      * This will also initialize the associated TextToSpeech engine if it isn't already running.
    687      *
    688      * @param context
    689      *            The context this instance is running in.
    690      * @param listener
    691      *            The {@link TextToSpeech.OnInitListener} that will be called when the
    692      *            TextToSpeech engine has initialized. In a case of a failure the listener
    693      *            may be called immediately, before TextToSpeech instance is fully constructed.
    694      */
    695     public TextToSpeech(Context context, OnInitListener listener) {
    696         this(context, listener, null);
    697     }
    698 
    699     /**
    700      * The constructor for the TextToSpeech class, using the given TTS engine.
    701      * This will also initialize the associated TextToSpeech engine if it isn't already running.
    702      *
    703      * @param context
    704      *            The context this instance is running in.
    705      * @param listener
    706      *            The {@link TextToSpeech.OnInitListener} that will be called when the
    707      *            TextToSpeech engine has initialized. In a case of a failure the listener
    708      *            may be called immediately, before TextToSpeech instance is fully constructed.
    709      * @param engine Package name of the TTS engine to use.
    710      */
    711     public TextToSpeech(Context context, OnInitListener listener, String engine) {
    712         this(context, listener, engine, null, true);
    713     }
    714 
    715     /**
    716      * Used by the framework to instantiate TextToSpeech objects with a supplied
    717      * package name, instead of using {@link android.content.Context#getPackageName()}
    718      *
    719      * @hide
    720      */
    721     public TextToSpeech(Context context, OnInitListener listener, String engine,
    722             String packageName, boolean useFallback) {
    723         mContext = context;
    724         mInitListener = listener;
    725         mRequestedEngine = engine;
    726         mUseFallback = useFallback;
    727 
    728         mEarcons = new HashMap<String, Uri>();
    729         mUtterances = new HashMap<CharSequence, Uri>();
    730         mUtteranceProgressListener = null;
    731 
    732         mEnginesHelper = new TtsEngines(mContext);
    733         initTts();
    734     }
    735 
    736     private <R> R runActionNoReconnect(Action<R> action, R errorResult, String method,
    737             boolean onlyEstablishedConnection) {
    738         return runAction(action, errorResult, method, false, onlyEstablishedConnection);
    739     }
    740 
    741     private <R> R runAction(Action<R> action, R errorResult, String method) {
    742         return runAction(action, errorResult, method, true, true);
    743     }
    744 
    745     private <R> R runAction(Action<R> action, R errorResult, String method,
    746             boolean reconnect, boolean onlyEstablishedConnection) {
    747         synchronized (mStartLock) {
    748             if (mServiceConnection == null) {
    749                 Log.w(TAG, method + " failed: not bound to TTS engine");
    750                 return errorResult;
    751             }
    752             return mServiceConnection.runAction(action, errorResult, method, reconnect,
    753                     onlyEstablishedConnection);
    754         }
    755     }
    756 
    757     private int initTts() {
    758         // Step 1: Try connecting to the engine that was requested.
    759         if (mRequestedEngine != null) {
    760             if (mEnginesHelper.isEngineInstalled(mRequestedEngine)) {
    761                 if (connectToEngine(mRequestedEngine)) {
    762                     mCurrentEngine = mRequestedEngine;
    763                     return SUCCESS;
    764                 } else if (!mUseFallback) {
    765                     mCurrentEngine = null;
    766                     dispatchOnInit(ERROR);
    767                     return ERROR;
    768                 }
    769             } else if (!mUseFallback) {
    770                 Log.i(TAG, "Requested engine not installed: " + mRequestedEngine);
    771                 mCurrentEngine = null;
    772                 dispatchOnInit(ERROR);
    773                 return ERROR;
    774             }
    775         }
    776 
    777         // Step 2: Try connecting to the user's default engine.
    778         final String defaultEngine = getDefaultEngine();
    779         if (defaultEngine != null && !defaultEngine.equals(mRequestedEngine)) {
    780             if (connectToEngine(defaultEngine)) {
    781                 mCurrentEngine = defaultEngine;
    782                 return SUCCESS;
    783             }
    784         }
    785 
    786         // Step 3: Try connecting to the highest ranked engine in the
    787         // system.
    788         final String highestRanked = mEnginesHelper.getHighestRankedEngineName();
    789         if (highestRanked != null && !highestRanked.equals(mRequestedEngine) &&
    790                 !highestRanked.equals(defaultEngine)) {
    791             if (connectToEngine(highestRanked)) {
    792                 mCurrentEngine = highestRanked;
    793                 return SUCCESS;
    794             }
    795         }
    796 
    797         // NOTE: The API currently does not allow the caller to query whether
    798         // they are actually connected to any engine. This might fail for various
    799         // reasons like if the user disables all her TTS engines.
    800 
    801         mCurrentEngine = null;
    802         dispatchOnInit(ERROR);
    803         return ERROR;
    804     }
    805 
    806     private boolean connectToEngine(String engine) {
    807         Connection connection = new Connection();
    808         Intent intent = new Intent(Engine.INTENT_ACTION_TTS_SERVICE);
    809         intent.setPackage(engine);
    810         boolean bound = mContext.bindService(intent, connection, Context.BIND_AUTO_CREATE);
    811         if (!bound) {
    812             Log.e(TAG, "Failed to bind to " + engine);
    813             return false;
    814         } else {
    815             Log.i(TAG, "Sucessfully bound to " + engine);
    816             mConnectingServiceConnection = connection;
    817             return true;
    818         }
    819     }
    820 
    821     private void dispatchOnInit(int result) {
    822         synchronized (mStartLock) {
    823             if (mInitListener != null) {
    824                 mInitListener.onInit(result);
    825                 mInitListener = null;
    826             }
    827         }
    828     }
    829 
    830     private IBinder getCallerIdentity() {
    831         return mServiceConnection.getCallerIdentity();
    832     }
    833 
    834     /**
    835      * Releases the resources used by the TextToSpeech engine.
    836      * It is good practice for instance to call this method in the onDestroy() method of an Activity
    837      * so the TextToSpeech engine can be cleanly stopped.
    838      */
    839     public void shutdown() {
    840         // Special case, we are asked to shutdown connection that did finalize its connection.
    841         synchronized (mStartLock) {
    842             if (mConnectingServiceConnection != null) {
    843                 mContext.unbindService(mConnectingServiceConnection);
    844                 mConnectingServiceConnection = null;
    845                 return;
    846             }
    847         }
    848 
    849         // Post connection case
    850         runActionNoReconnect(new Action<Void>() {
    851             @Override
    852             public Void run(ITextToSpeechService service) throws RemoteException {
    853                 service.setCallback(getCallerIdentity(), null);
    854                 service.stop(getCallerIdentity());
    855                 mServiceConnection.disconnect();
    856                 // Context#unbindService does not result in a call to
    857                 // ServiceConnection#onServiceDisconnected. As a result, the
    858                 // service ends up being destroyed (if there are no other open
    859                 // connections to it) but the process lives on and the
    860                 // ServiceConnection continues to refer to the destroyed service.
    861                 //
    862                 // This leads to tons of log spam about SynthThread being dead.
    863                 mServiceConnection = null;
    864                 mCurrentEngine = null;
    865                 return null;
    866             }
    867         }, null, "shutdown", false);
    868     }
    869 
    870     /**
    871      * Adds a mapping between a string of text and a sound resource in a
    872      * package. After a call to this method, subsequent calls to
    873      * {@link #speak(String, int, HashMap)} will play the specified sound resource
    874      * if it is available, or synthesize the text it is missing.
    875      *
    876      * @param text
    877      *            The string of text. Example: <code>"south_south_east"</code>
    878      *
    879      * @param packagename
    880      *            Pass the packagename of the application that contains the
    881      *            resource. If the resource is in your own application (this is
    882      *            the most common case), then put the packagename of your
    883      *            application here.<br/>
    884      *            Example: <b>"com.google.marvin.compass"</b><br/>
    885      *            The packagename can be found in the AndroidManifest.xml of
    886      *            your application.
    887      *            <p>
    888      *            <code>&lt;manifest xmlns:android=&quot;...&quot;
    889      *      package=&quot;<b>com.google.marvin.compass</b>&quot;&gt;</code>
    890      *            </p>
    891      *
    892      * @param resourceId
    893      *            Example: <code>R.raw.south_south_east</code>
    894      *
    895      * @return Code indicating success or failure. See {@link #ERROR} and {@link #SUCCESS}.
    896      */
    897     public int addSpeech(String text, String packagename, @RawRes int resourceId) {
    898         synchronized (mStartLock) {
    899             mUtterances.put(text, makeResourceUri(packagename, resourceId));
    900             return SUCCESS;
    901         }
    902     }
    903 
    904     /**
    905      * Adds a mapping between a CharSequence (may be spanned with TtsSpans) of text
    906      * and a sound resource in a package. After a call to this method, subsequent calls to
    907      * {@link #speak(String, int, HashMap)} will play the specified sound resource
    908      * if it is available, or synthesize the text it is missing.
    909      *
    910      * @param text
    911      *            The string of text. Example: <code>"south_south_east"</code>
    912      *
    913      * @param packagename
    914      *            Pass the packagename of the application that contains the
    915      *            resource. If the resource is in your own application (this is
    916      *            the most common case), then put the packagename of your
    917      *            application here.<br/>
    918      *            Example: <b>"com.google.marvin.compass"</b><br/>
    919      *            The packagename can be found in the AndroidManifest.xml of
    920      *            your application.
    921      *            <p>
    922      *            <code>&lt;manifest xmlns:android=&quot;...&quot;
    923      *      package=&quot;<b>com.google.marvin.compass</b>&quot;&gt;</code>
    924      *            </p>
    925      *
    926      * @param resourceId
    927      *            Example: <code>R.raw.south_south_east</code>
    928      *
    929      * @return Code indicating success or failure. See {@link #ERROR} and {@link #SUCCESS}.
    930      */
    931     public int addSpeech(CharSequence text, String packagename, @RawRes int resourceId) {
    932         synchronized (mStartLock) {
    933             mUtterances.put(text, makeResourceUri(packagename, resourceId));
    934             return SUCCESS;
    935         }
    936     }
    937 
    938     /**
    939      * Adds a mapping between a string of text and a sound file. Using this, it
    940      * is possible to add custom pronounciations for a string of text.
    941      * After a call to this method, subsequent calls to {@link #speak(String, int, HashMap)}
    942      * will play the specified sound resource if it is available, or synthesize the text it is
    943      * missing.
    944      *
    945      * @param text
    946      *            The string of text. Example: <code>"south_south_east"</code>
    947      * @param filename
    948      *            The full path to the sound file (for example:
    949      *            "/sdcard/mysounds/hello.wav")
    950      *
    951      * @return Code indicating success or failure. See {@link #ERROR} and {@link #SUCCESS}.
    952      */
    953     public int addSpeech(String text, String filename) {
    954         synchronized (mStartLock) {
    955             mUtterances.put(text, Uri.parse(filename));
    956             return SUCCESS;
    957         }
    958     }
    959 
    960     /**
    961      * Adds a mapping between a CharSequence (may be spanned with TtsSpans and a sound file.
    962      * Using this, it is possible to add custom pronounciations for a string of text.
    963      * After a call to this method, subsequent calls to {@link #speak(String, int, HashMap)}
    964      * will play the specified sound resource if it is available, or synthesize the text it is
    965      * missing.
    966      *
    967      * @param text
    968      *            The string of text. Example: <code>"south_south_east"</code>
    969      * @param file
    970      *            File object pointing to the sound file.
    971      *
    972      * @return Code indicating success or failure. See {@link #ERROR} and {@link #SUCCESS}.
    973      */
    974     public int addSpeech(CharSequence text, File file) {
    975         synchronized (mStartLock) {
    976             mUtterances.put(text, Uri.fromFile(file));
    977             return SUCCESS;
    978         }
    979     }
    980 
    981     /**
    982      * Adds a mapping between a string of text and a sound resource in a
    983      * package. Use this to add custom earcons.
    984      *
    985      * @see #playEarcon(String, int, HashMap)
    986      *
    987      * @param earcon The name of the earcon.
    988      *            Example: <code>"[tick]"</code><br/>
    989      *
    990      * @param packagename
    991      *            the package name of the application that contains the
    992      *            resource. This can for instance be the package name of your own application.
    993      *            Example: <b>"com.google.marvin.compass"</b><br/>
    994      *            The package name can be found in the AndroidManifest.xml of
    995      *            the application containing the resource.
    996      *            <p>
    997      *            <code>&lt;manifest xmlns:android=&quot;...&quot;
    998      *      package=&quot;<b>com.google.marvin.compass</b>&quot;&gt;</code>
    999      *            </p>
   1000      *
   1001      * @param resourceId
   1002      *            Example: <code>R.raw.tick_snd</code>
   1003      *
   1004      * @return Code indicating success or failure. See {@link #ERROR} and {@link #SUCCESS}.
   1005      */
   1006     public int addEarcon(String earcon, String packagename, @RawRes int resourceId) {
   1007         synchronized(mStartLock) {
   1008             mEarcons.put(earcon, makeResourceUri(packagename, resourceId));
   1009             return SUCCESS;
   1010         }
   1011     }
   1012 
   1013     /**
   1014      * Adds a mapping between a string of text and a sound file.
   1015      * Use this to add custom earcons.
   1016      *
   1017      * @see #playEarcon(String, int, HashMap)
   1018      *
   1019      * @param earcon
   1020      *            The name of the earcon.
   1021      *            Example: <code>"[tick]"</code>
   1022      * @param filename
   1023      *            The full path to the sound file (for example:
   1024      *            "/sdcard/mysounds/tick.wav")
   1025      *
   1026      * @return Code indicating success or failure. See {@link #ERROR} and {@link #SUCCESS}.
   1027      *
   1028      * @deprecated As of API level 21, replaced by
   1029      *         {@link #addEarcon(String, File)}.
   1030      */
   1031     @Deprecated
   1032     public int addEarcon(String earcon, String filename) {
   1033         synchronized(mStartLock) {
   1034             mEarcons.put(earcon, Uri.parse(filename));
   1035             return SUCCESS;
   1036         }
   1037     }
   1038 
   1039     /**
   1040      * Adds a mapping between a string of text and a sound file.
   1041      * Use this to add custom earcons.
   1042      *
   1043      * @see #playEarcon(String, int, HashMap)
   1044      *
   1045      * @param earcon
   1046      *            The name of the earcon.
   1047      *            Example: <code>"[tick]"</code>
   1048      * @param file
   1049      *            File object pointing to the sound file.
   1050      *
   1051      * @return Code indicating success or failure. See {@link #ERROR} and {@link #SUCCESS}.
   1052      */
   1053     public int addEarcon(String earcon, File file) {
   1054         synchronized(mStartLock) {
   1055             mEarcons.put(earcon, Uri.fromFile(file));
   1056             return SUCCESS;
   1057         }
   1058     }
   1059 
   1060     private Uri makeResourceUri(String packageName, int resourceId) {
   1061         return new Uri.Builder()
   1062                 .scheme(ContentResolver.SCHEME_ANDROID_RESOURCE)
   1063                 .encodedAuthority(packageName)
   1064                 .appendEncodedPath(String.valueOf(resourceId))
   1065                 .build();
   1066     }
   1067 
   1068     /**
   1069      * Speaks the text using the specified queuing strategy and speech parameters, the text may
   1070      * be spanned with TtsSpans.
   1071      * This method is asynchronous, i.e. the method just adds the request to the queue of TTS
   1072      * requests and then returns. The synthesis might not have finished (or even started!) at the
   1073      * time when this method returns. In order to reliably detect errors during synthesis,
   1074      * we recommend setting an utterance progress listener (see
   1075      * {@link #setOnUtteranceProgressListener}) and using the
   1076      * {@link Engine#KEY_PARAM_UTTERANCE_ID} parameter.
   1077      *
   1078      * @param text The string of text to be spoken. No longer than
   1079      *            {@link #getMaxSpeechInputLength()} characters.
   1080      * @param queueMode The queuing strategy to use, {@link #QUEUE_ADD} or {@link #QUEUE_FLUSH}.
   1081      * @param params Parameters for the request. Can be null.
   1082      *            Supported parameter names:
   1083      *            {@link Engine#KEY_PARAM_STREAM},
   1084      *            {@link Engine#KEY_PARAM_VOLUME},
   1085      *            {@link Engine#KEY_PARAM_PAN}.
   1086      *            Engine specific parameters may be passed in but the parameter keys
   1087      *            must be prefixed by the name of the engine they are intended for. For example
   1088      *            the keys "com.svox.pico_foo" and "com.svox.pico:bar" will be passed to the
   1089      *            engine named "com.svox.pico" if it is being used.
   1090      * @param utteranceId An unique identifier for this request.
   1091      *
   1092      * @return {@link #ERROR} or {@link #SUCCESS} of <b>queuing</b> the speak operation.
   1093      */
   1094     public int speak(final CharSequence text,
   1095                      final int queueMode,
   1096                      final Bundle params,
   1097                      final String utteranceId) {
   1098         return runAction(new Action<Integer>() {
   1099             @Override
   1100             public Integer run(ITextToSpeechService service) throws RemoteException {
   1101                 Uri utteranceUri = mUtterances.get(text);
   1102                 if (utteranceUri != null) {
   1103                     return service.playAudio(getCallerIdentity(), utteranceUri, queueMode,
   1104                             getParams(params), utteranceId);
   1105                 } else {
   1106                     return service.speak(getCallerIdentity(), text, queueMode, getParams(params),
   1107                             utteranceId);
   1108                 }
   1109             }
   1110         }, ERROR, "speak");
   1111     }
   1112 
   1113     /**
   1114      * Speaks the string using the specified queuing strategy and speech parameters.
   1115      * This method is asynchronous, i.e. the method just adds the request to the queue of TTS
   1116      * requests and then returns. The synthesis might not have finished (or even started!) at the
   1117      * time when this method returns. In order to reliably detect errors during synthesis,
   1118      * we recommend setting an utterance progress listener (see
   1119      * {@link #setOnUtteranceProgressListener}) and using the
   1120      * {@link Engine#KEY_PARAM_UTTERANCE_ID} parameter.
   1121      *
   1122      * @param text The string of text to be spoken. No longer than
   1123      *            {@link #getMaxSpeechInputLength()} characters.
   1124      * @param queueMode The queuing strategy to use, {@link #QUEUE_ADD} or {@link #QUEUE_FLUSH}.
   1125      * @param params Parameters for the request. Can be null.
   1126      *            Supported parameter names:
   1127      *            {@link Engine#KEY_PARAM_STREAM},
   1128      *            {@link Engine#KEY_PARAM_UTTERANCE_ID},
   1129      *            {@link Engine#KEY_PARAM_VOLUME},
   1130      *            {@link Engine#KEY_PARAM_PAN}.
   1131      *            Engine specific parameters may be passed in but the parameter keys
   1132      *            must be prefixed by the name of the engine they are intended for. For example
   1133      *            the keys "com.svox.pico_foo" and "com.svox.pico:bar" will be passed to the
   1134      *            engine named "com.svox.pico" if it is being used.
   1135      *
   1136      * @return {@link #ERROR} or {@link #SUCCESS} of <b>queuing</b> the speak operation.
   1137      * @deprecated As of API level 21, replaced by
   1138      *         {@link #speak(CharSequence, int, Bundle, String)}.
   1139      */
   1140     @Deprecated
   1141     public int speak(final String text, final int queueMode, final HashMap<String, String> params) {
   1142         return speak(text, queueMode, convertParamsHashMaptoBundle(params),
   1143                      params == null ? null : params.get(Engine.KEY_PARAM_UTTERANCE_ID));
   1144     }
   1145 
   1146     /**
   1147      * Plays the earcon using the specified queueing mode and parameters.
   1148      * The earcon must already have been added with {@link #addEarcon(String, String)} or
   1149      * {@link #addEarcon(String, String, int)}.
   1150      * This method is asynchronous, i.e. the method just adds the request to the queue of TTS
   1151      * requests and then returns. The synthesis might not have finished (or even started!) at the
   1152      * time when this method returns. In order to reliably detect errors during synthesis,
   1153      * we recommend setting an utterance progress listener (see
   1154      * {@link #setOnUtteranceProgressListener}) and using the
   1155      * {@link Engine#KEY_PARAM_UTTERANCE_ID} parameter.
   1156      *
   1157      * @param earcon The earcon that should be played
   1158      * @param queueMode {@link #QUEUE_ADD} or {@link #QUEUE_FLUSH}.
   1159      * @param params Parameters for the request. Can be null.
   1160      *            Supported parameter names:
   1161      *            {@link Engine#KEY_PARAM_STREAM},
   1162      *            Engine specific parameters may be passed in but the parameter keys
   1163      *            must be prefixed by the name of the engine they are intended for. For example
   1164      *            the keys "com.svox.pico_foo" and "com.svox.pico:bar" will be passed to the
   1165      *            engine named "com.svox.pico" if it is being used.
   1166      *
   1167      * @return {@link #ERROR} or {@link #SUCCESS} of <b>queuing</b> the playEarcon operation.
   1168      */
   1169     public int playEarcon(final String earcon, final int queueMode,
   1170             final Bundle params, final String utteranceId) {
   1171         return runAction(new Action<Integer>() {
   1172             @Override
   1173             public Integer run(ITextToSpeechService service) throws RemoteException {
   1174                 Uri earconUri = mEarcons.get(earcon);
   1175                 if (earconUri == null) {
   1176                     return ERROR;
   1177                 }
   1178                 return service.playAudio(getCallerIdentity(), earconUri, queueMode,
   1179                         getParams(params), utteranceId);
   1180             }
   1181         }, ERROR, "playEarcon");
   1182     }
   1183 
   1184     /**
   1185      * Plays the earcon using the specified queueing mode and parameters.
   1186      * The earcon must already have been added with {@link #addEarcon(String, String)} or
   1187      * {@link #addEarcon(String, String, int)}.
   1188      * This method is asynchronous, i.e. the method just adds the request to the queue of TTS
   1189      * requests and then returns. The synthesis might not have finished (or even started!) at the
   1190      * time when this method returns. In order to reliably detect errors during synthesis,
   1191      * we recommend setting an utterance progress listener (see
   1192      * {@link #setOnUtteranceProgressListener}) and using the
   1193      * {@link Engine#KEY_PARAM_UTTERANCE_ID} parameter.
   1194      *
   1195      * @param earcon The earcon that should be played
   1196      * @param queueMode {@link #QUEUE_ADD} or {@link #QUEUE_FLUSH}.
   1197      * @param params Parameters for the request. Can be null.
   1198      *            Supported parameter names:
   1199      *            {@link Engine#KEY_PARAM_STREAM},
   1200      *            {@link Engine#KEY_PARAM_UTTERANCE_ID}.
   1201      *            Engine specific parameters may be passed in but the parameter keys
   1202      *            must be prefixed by the name of the engine they are intended for. For example
   1203      *            the keys "com.svox.pico_foo" and "com.svox.pico:bar" will be passed to the
   1204      *            engine named "com.svox.pico" if it is being used.
   1205      *
   1206      * @return {@link #ERROR} or {@link #SUCCESS} of <b>queuing</b> the playEarcon operation.
   1207      * @deprecated As of API level 21, replaced by
   1208      *         {@link #playEarcon(String, int, Bundle, String)}.
   1209      */
   1210     @Deprecated
   1211     public int playEarcon(final String earcon, final int queueMode,
   1212             final HashMap<String, String> params) {
   1213         return playEarcon(earcon, queueMode, convertParamsHashMaptoBundle(params),
   1214                           params == null ? null : params.get(Engine.KEY_PARAM_UTTERANCE_ID));
   1215     }
   1216 
   1217     /**
   1218      * Plays silence for the specified amount of time using the specified
   1219      * queue mode.
   1220      * This method is asynchronous, i.e. the method just adds the request to the queue of TTS
   1221      * requests and then returns. The synthesis might not have finished (or even started!) at the
   1222      * time when this method returns. In order to reliably detect errors during synthesis,
   1223      * we recommend setting an utterance progress listener (see
   1224      * {@link #setOnUtteranceProgressListener}) and using the
   1225      * {@link Engine#KEY_PARAM_UTTERANCE_ID} parameter.
   1226      *
   1227      * @param durationInMs The duration of the silence.
   1228      * @param queueMode {@link #QUEUE_ADD} or {@link #QUEUE_FLUSH}.
   1229      * @param utteranceId An unique identifier for this request.
   1230      *
   1231      * @return {@link #ERROR} or {@link #SUCCESS} of <b>queuing</b> the playSilentUtterance operation.
   1232      */
   1233     public int playSilentUtterance(final long durationInMs, final int queueMode,
   1234             final String utteranceId) {
   1235         return runAction(new Action<Integer>() {
   1236             @Override
   1237             public Integer run(ITextToSpeechService service) throws RemoteException {
   1238                 return service.playSilence(getCallerIdentity(), durationInMs,
   1239                                            queueMode, utteranceId);
   1240             }
   1241         }, ERROR, "playSilentUtterance");
   1242     }
   1243 
   1244     /**
   1245      * Plays silence for the specified amount of time using the specified
   1246      * queue mode.
   1247      * This method is asynchronous, i.e. the method just adds the request to the queue of TTS
   1248      * requests and then returns. The synthesis might not have finished (or even started!) at the
   1249      * time when this method returns. In order to reliably detect errors during synthesis,
   1250      * we recommend setting an utterance progress listener (see
   1251      * {@link #setOnUtteranceProgressListener}) and using the
   1252      * {@link Engine#KEY_PARAM_UTTERANCE_ID} parameter.
   1253      *
   1254      * @param durationInMs The duration of the silence.
   1255      * @param queueMode {@link #QUEUE_ADD} or {@link #QUEUE_FLUSH}.
   1256      * @param params Parameters for the request. Can be null.
   1257      *            Supported parameter names:
   1258      *            {@link Engine#KEY_PARAM_UTTERANCE_ID}.
   1259      *            Engine specific parameters may be passed in but the parameter keys
   1260      *            must be prefixed by the name of the engine they are intended for. For example
   1261      *            the keys "com.svox.pico_foo" and "com.svox.pico:bar" will be passed to the
   1262      *            engine named "com.svox.pico" if it is being used.
   1263      *
   1264      * @return {@link #ERROR} or {@link #SUCCESS} of <b>queuing</b> the playSilence operation.
   1265      * @deprecated As of API level 21, replaced by
   1266      *         {@link #playSilentUtterance(long, int, String)}.
   1267      */
   1268     @Deprecated
   1269     public int playSilence(final long durationInMs, final int queueMode,
   1270             final HashMap<String, String> params) {
   1271         return playSilentUtterance(durationInMs, queueMode,
   1272                            params == null ? null : params.get(Engine.KEY_PARAM_UTTERANCE_ID));
   1273     }
   1274 
   1275     /**
   1276      * Queries the engine for the set of features it supports for a given locale.
   1277      * Features can either be framework defined, e.g.
   1278      * {@link TextToSpeech.Engine#KEY_FEATURE_NETWORK_SYNTHESIS} or engine specific.
   1279      * Engine specific keys must be prefixed by the name of the engine they
   1280      * are intended for. These keys can be used as parameters to
   1281      * {@link TextToSpeech#speak(String, int, java.util.HashMap)} and
   1282      * {@link TextToSpeech#synthesizeToFile(String, java.util.HashMap, String)}.
   1283      *
   1284      * Features values are strings and their values must meet restrictions described in their
   1285      * documentation.
   1286      *
   1287      * @param locale The locale to query features for.
   1288      * @return Set instance. May return {@code null} on error.
   1289      * @deprecated As of API level 21, please use voices. In order to query features of the voice,
   1290      * call {@link #getVoices()} to retrieve the list of available voices and
   1291      * {@link Voice#getFeatures()} to retrieve the set of features.
   1292      */
   1293     @Deprecated
   1294     public Set<String> getFeatures(final Locale locale) {
   1295         return runAction(new Action<Set<String>>() {
   1296             @Override
   1297             public Set<String> run(ITextToSpeechService service) throws RemoteException {
   1298                 String[] features = null;
   1299                 try {
   1300                     features = service.getFeaturesForLanguage(
   1301                         locale.getISO3Language(), locale.getISO3Country(), locale.getVariant());
   1302                 } catch(MissingResourceException e) {
   1303                     Log.w(TAG, "Couldn't retrieve 3 letter ISO 639-2/T language and/or ISO 3166 " +
   1304                             "country code for locale: " + locale, e);
   1305                     return null;
   1306                 }
   1307 
   1308                 if (features != null) {
   1309                     final Set<String> featureSet = new HashSet<String>();
   1310                     Collections.addAll(featureSet, features);
   1311                     return featureSet;
   1312                 }
   1313                 return null;
   1314             }
   1315         }, null, "getFeatures");
   1316     }
   1317 
   1318     /**
   1319      * Checks whether the TTS engine is busy speaking. Note that a speech item is
   1320      * considered complete once it's audio data has been sent to the audio mixer, or
   1321      * written to a file. There might be a finite lag between this point, and when
   1322      * the audio hardware completes playback.
   1323      *
   1324      * @return {@code true} if the TTS engine is speaking.
   1325      */
   1326     public boolean isSpeaking() {
   1327         return runAction(new Action<Boolean>() {
   1328             @Override
   1329             public Boolean run(ITextToSpeechService service) throws RemoteException {
   1330                 return service.isSpeaking();
   1331             }
   1332         }, false, "isSpeaking");
   1333     }
   1334 
   1335     /**
   1336      * Interrupts the current utterance (whether played or rendered to file) and discards other
   1337      * utterances in the queue.
   1338      *
   1339      * @return {@link #ERROR} or {@link #SUCCESS}.
   1340      */
   1341     public int stop() {
   1342         return runAction(new Action<Integer>() {
   1343             @Override
   1344             public Integer run(ITextToSpeechService service) throws RemoteException {
   1345                 return service.stop(getCallerIdentity());
   1346             }
   1347         }, ERROR, "stop");
   1348     }
   1349 
   1350     /**
   1351      * Sets the speech rate.
   1352      *
   1353      * This has no effect on any pre-recorded speech.
   1354      *
   1355      * @param speechRate Speech rate. {@code 1.0} is the normal speech rate,
   1356      *            lower values slow down the speech ({@code 0.5} is half the normal speech rate),
   1357      *            greater values accelerate it ({@code 2.0} is twice the normal speech rate).
   1358      *
   1359      * @return {@link #ERROR} or {@link #SUCCESS}.
   1360      */
   1361     public int setSpeechRate(float speechRate) {
   1362         if (speechRate > 0.0f) {
   1363             int intRate = (int)(speechRate * 100);
   1364             if (intRate > 0) {
   1365                 synchronized (mStartLock) {
   1366                     mParams.putInt(Engine.KEY_PARAM_RATE, intRate);
   1367                 }
   1368                 return SUCCESS;
   1369             }
   1370         }
   1371         return ERROR;
   1372     }
   1373 
   1374     /**
   1375      * Sets the speech pitch for the TextToSpeech engine.
   1376      *
   1377      * This has no effect on any pre-recorded speech.
   1378      *
   1379      * @param pitch Speech pitch. {@code 1.0} is the normal pitch,
   1380      *            lower values lower the tone of the synthesized voice,
   1381      *            greater values increase it.
   1382      *
   1383      * @return {@link #ERROR} or {@link #SUCCESS}.
   1384      */
   1385     public int setPitch(float pitch) {
   1386         if (pitch > 0.0f) {
   1387             int intPitch = (int)(pitch * 100);
   1388             if (intPitch > 0) {
   1389                 synchronized (mStartLock) {
   1390                     mParams.putInt(Engine.KEY_PARAM_PITCH, intPitch);
   1391                 }
   1392                 return SUCCESS;
   1393             }
   1394         }
   1395         return ERROR;
   1396     }
   1397 
   1398     /**
   1399      * Sets the audio attributes to be used when speaking text or playing
   1400      * back a file.
   1401      *
   1402      * @param audioAttributes Valid AudioAttributes instance.
   1403      *
   1404      * @return {@link #ERROR} or {@link #SUCCESS}.
   1405      */
   1406     public int setAudioAttributes(AudioAttributes audioAttributes) {
   1407         if (audioAttributes != null) {
   1408             synchronized (mStartLock) {
   1409                 mParams.putParcelable(Engine.KEY_PARAM_AUDIO_ATTRIBUTES,
   1410                     audioAttributes);
   1411             }
   1412             return SUCCESS;
   1413         }
   1414         return ERROR;
   1415     }
   1416 
   1417     /**
   1418      * @return the engine currently in use by this TextToSpeech instance.
   1419      * @hide
   1420      */
   1421     public String getCurrentEngine() {
   1422         return mCurrentEngine;
   1423     }
   1424 
   1425     /**
   1426      * Returns a Locale instance describing the language currently being used as the default
   1427      * Text-to-speech language.
   1428      *
   1429      * The locale object returned by this method is NOT a valid one. It has identical form to the
   1430      * one in {@link #getLanguage()}. Please refer to {@link #getLanguage()} for more information.
   1431      *
   1432      * @return language, country (if any) and variant (if any) used by the client stored in a
   1433      *     Locale instance, or {@code null} on error.
   1434      * @deprecated As of API level 21, use <code>getDefaultVoice().getLocale()</code> ({@link
   1435      *   #getDefaultVoice()})
   1436      */
   1437     @Deprecated
   1438     public Locale getDefaultLanguage() {
   1439         return runAction(new Action<Locale>() {
   1440             @Override
   1441             public Locale run(ITextToSpeechService service) throws RemoteException {
   1442                 String[] defaultLanguage = service.getClientDefaultLanguage();
   1443 
   1444                 return new Locale(defaultLanguage[0], defaultLanguage[1], defaultLanguage[2]);
   1445             }
   1446         }, null, "getDefaultLanguage");
   1447     }
   1448 
   1449     /**
   1450      * Sets the text-to-speech language.
   1451      * The TTS engine will try to use the closest match to the specified
   1452      * language as represented by the Locale, but there is no guarantee that the exact same Locale
   1453      * will be used. Use {@link #isLanguageAvailable(Locale)} to check the level of support
   1454      * before choosing the language to use for the next utterances.
   1455      *
   1456      * This method sets the current voice to the default one for the given Locale;
   1457      * {@link #getVoice()} can be used to retrieve it.
   1458      *
   1459      * @param loc The locale describing the language to be used.
   1460      *
   1461      * @return Code indicating the support status for the locale. See {@link #LANG_AVAILABLE},
   1462      *         {@link #LANG_COUNTRY_AVAILABLE}, {@link #LANG_COUNTRY_VAR_AVAILABLE},
   1463      *         {@link #LANG_MISSING_DATA} and {@link #LANG_NOT_SUPPORTED}.
   1464      */
   1465     public int setLanguage(final Locale loc) {
   1466         return runAction(new Action<Integer>() {
   1467             @Override
   1468             public Integer run(ITextToSpeechService service) throws RemoteException {
   1469                 if (loc == null) {
   1470                     return LANG_NOT_SUPPORTED;
   1471                 }
   1472                 String language = null, country = null;
   1473                 try {
   1474                     language = loc.getISO3Language();
   1475                 } catch (MissingResourceException e) {
   1476                     Log.w(TAG, "Couldn't retrieve ISO 639-2/T language code for locale: " + loc, e);
   1477                     return LANG_NOT_SUPPORTED;
   1478                 }
   1479 
   1480                 try {
   1481                     country = loc.getISO3Country();
   1482                 } catch (MissingResourceException e) {
   1483                     Log.w(TAG, "Couldn't retrieve ISO 3166 country code for locale: " + loc, e);
   1484                     return LANG_NOT_SUPPORTED;
   1485                 }
   1486 
   1487                 String variant = loc.getVariant();
   1488 
   1489                 // As of API level 21, setLanguage is implemented using setVoice.
   1490                 // (which, in the default implementation, will call loadLanguage on the service
   1491                 // interface).
   1492 
   1493                 // Sanitize locale using isLanguageAvailable.
   1494                 int result = service.isLanguageAvailable(language, country, variant);
   1495                 if (result >= LANG_AVAILABLE) {
   1496                     // Get the default voice for the locale.
   1497                     String voiceName = service.getDefaultVoiceNameFor(language, country, variant);
   1498                     if (TextUtils.isEmpty(voiceName)) {
   1499                         Log.w(TAG, "Couldn't find the default voice for " + language + "-" +
   1500                                 country + "-" + variant);
   1501                         return LANG_NOT_SUPPORTED;
   1502                     }
   1503 
   1504                     // Load it.
   1505                     if (service.loadVoice(getCallerIdentity(), voiceName) == TextToSpeech.ERROR) {
   1506                         Log.w(TAG, "The service claimed " + language + "-" + country + "-"
   1507                                 + variant + " was available with voice name " + voiceName
   1508                                 + " but loadVoice returned ERROR");
   1509                         return LANG_NOT_SUPPORTED;
   1510                     }
   1511 
   1512                     // Set the language/country/variant of the voice, so #getLanguage will return
   1513                     // the currently set voice locale when called.
   1514                     Voice voice = getVoice(service, voiceName);
   1515                     if (voice == null) {
   1516                         Log.w(TAG, "getDefaultVoiceNameFor returned " + voiceName + " for locale "
   1517                                 + language + "-" + country + "-" + variant
   1518                                 + " but getVoice returns null");
   1519                         return LANG_NOT_SUPPORTED;
   1520                     }
   1521                     String voiceLanguage = "";
   1522                     try {
   1523                         voiceLanguage = voice.getLocale().getISO3Language();
   1524                     } catch (MissingResourceException e) {
   1525                         Log.w(TAG, "Couldn't retrieve ISO 639-2/T language code for locale: " +
   1526                                 voice.getLocale(), e);
   1527                     }
   1528 
   1529                     String voiceCountry = "";
   1530                     try {
   1531                         voiceCountry = voice.getLocale().getISO3Country();
   1532                     } catch (MissingResourceException e) {
   1533                         Log.w(TAG, "Couldn't retrieve ISO 3166 country code for locale: " +
   1534                                 voice.getLocale(), e);
   1535                     }
   1536                     mParams.putString(Engine.KEY_PARAM_VOICE_NAME, voiceName);
   1537                     mParams.putString(Engine.KEY_PARAM_LANGUAGE, voiceLanguage);
   1538                     mParams.putString(Engine.KEY_PARAM_COUNTRY, voiceCountry);
   1539                     mParams.putString(Engine.KEY_PARAM_VARIANT, voice.getLocale().getVariant());
   1540                 }
   1541                 return result;
   1542             }
   1543         }, LANG_NOT_SUPPORTED, "setLanguage");
   1544     }
   1545 
   1546     /**
   1547      * Returns a Locale instance describing the language currently being used for synthesis
   1548      * requests sent to the TextToSpeech engine.
   1549      *
   1550      * In Android 4.2 and before (API <= 17) this function returns the language that is currently
   1551      * being used by the TTS engine. That is the last language set by this or any other
   1552      * client by a {@link TextToSpeech#setLanguage} call to the same engine.
   1553      *
   1554      * In Android versions after 4.2 this function returns the language that is currently being
   1555      * used for the synthesis requests sent from this client. That is the last language set
   1556      * by a {@link TextToSpeech#setLanguage} call on this instance.
   1557      *
   1558      * If a voice is set (by {@link #setVoice(Voice)}), getLanguage will return the language of
   1559      * the currently set voice.
   1560      *
   1561      * Please note that the Locale object returned by this method is NOT a valid Locale object. Its
   1562      * language field contains a three-letter ISO 639-2/T code (where a proper Locale would use
   1563      * a two-letter ISO 639-1 code), and the country field contains a three-letter ISO 3166 country
   1564      * code (where a proper Locale would use a two-letter ISO 3166-1 code).
   1565      *
   1566      * @return language, country (if any) and variant (if any) used by the client stored in a
   1567      *     Locale instance, or {@code null} on error.
   1568      *
   1569      * @deprecated As of API level 21, please use <code>getVoice().getLocale()</code>
   1570      * ({@link #getVoice()}).
   1571      */
   1572     @Deprecated
   1573     public Locale getLanguage() {
   1574         return runAction(new Action<Locale>() {
   1575             @Override
   1576             public Locale run(ITextToSpeechService service) {
   1577                 /* No service call, but we're accessing mParams, hence need for
   1578                    wrapping it as an Action instance */
   1579                 String lang = mParams.getString(Engine.KEY_PARAM_LANGUAGE, "");
   1580                 String country = mParams.getString(Engine.KEY_PARAM_COUNTRY, "");
   1581                 String variant = mParams.getString(Engine.KEY_PARAM_VARIANT, "");
   1582                 return new Locale(lang, country, variant);
   1583             }
   1584         }, null, "getLanguage");
   1585     }
   1586 
   1587     /**
   1588      * Query the engine about the set of available languages.
   1589      */
   1590     public Set<Locale> getAvailableLanguages() {
   1591         return runAction(new Action<Set<Locale>>() {
   1592             @Override
   1593             public Set<Locale> run(ITextToSpeechService service) throws RemoteException {
   1594                 List<Voice> voices = service.getVoices();
   1595                 if (voices == null) {
   1596                     return new HashSet<Locale>();
   1597                 }
   1598                 HashSet<Locale> locales = new HashSet<Locale>();
   1599                 for (Voice voice : voices) {
   1600                     locales.add(voice.getLocale());
   1601                 }
   1602                 return locales;
   1603             }
   1604         }, null, "getAvailableLanguages");
   1605     }
   1606 
   1607     /**
   1608      * Query the engine about the set of available voices.
   1609      *
   1610      * Each TTS Engine can expose multiple voices for each locale, each with a different set of
   1611      * features.
   1612      *
   1613      * @see #setVoice(Voice)
   1614      * @see Voice
   1615      */
   1616     public Set<Voice> getVoices() {
   1617         return runAction(new Action<Set<Voice>>() {
   1618             @Override
   1619             public Set<Voice> run(ITextToSpeechService service) throws RemoteException {
   1620                 List<Voice> voices = service.getVoices();
   1621                 return (voices != null)  ? new HashSet<Voice>(voices) : new HashSet<Voice>();
   1622             }
   1623         }, null, "getVoices");
   1624     }
   1625 
   1626     /**
   1627      * Sets the text-to-speech voice.
   1628      *
   1629      * @param voice One of objects returned by {@link #getVoices()}.
   1630      *
   1631      * @return {@link #ERROR} or {@link #SUCCESS}.
   1632      *
   1633      * @see #getVoices
   1634      * @see Voice
   1635      */
   1636     public int setVoice(final Voice voice) {
   1637         return runAction(new Action<Integer>() {
   1638             @Override
   1639             public Integer run(ITextToSpeechService service) throws RemoteException {
   1640                 int result = service.loadVoice(getCallerIdentity(), voice.getName());
   1641                 if (result == SUCCESS) {
   1642                     mParams.putString(Engine.KEY_PARAM_VOICE_NAME, voice.getName());
   1643 
   1644                     // Set the language/country/variant, so #getLanguage will return the voice
   1645                     // locale when called.
   1646                     String language = "";
   1647                     try {
   1648                         language = voice.getLocale().getISO3Language();
   1649                     } catch (MissingResourceException e) {
   1650                         Log.w(TAG, "Couldn't retrieve ISO 639-2/T language code for locale: " +
   1651                                 voice.getLocale(), e);
   1652                     }
   1653 
   1654                     String country = "";
   1655                     try {
   1656                         country = voice.getLocale().getISO3Country();
   1657                     } catch (MissingResourceException e) {
   1658                         Log.w(TAG, "Couldn't retrieve ISO 3166 country code for locale: " +
   1659                                 voice.getLocale(), e);
   1660                     }
   1661                     mParams.putString(Engine.KEY_PARAM_LANGUAGE, language);
   1662                     mParams.putString(Engine.KEY_PARAM_COUNTRY, country);
   1663                     mParams.putString(Engine.KEY_PARAM_VARIANT, voice.getLocale().getVariant());
   1664                 }
   1665                 return result;
   1666             }
   1667         }, LANG_NOT_SUPPORTED, "setVoice");
   1668     }
   1669 
   1670     /**
   1671      * Returns a Voice instance describing the voice currently being used for synthesis
   1672      * requests sent to the TextToSpeech engine.
   1673      *
   1674      * @return Voice instance used by the client, or {@code null} if not set or on error.
   1675      *
   1676      * @see #getVoices
   1677      * @see #setVoice
   1678      * @see Voice
   1679      */
   1680     public Voice getVoice() {
   1681         return runAction(new Action<Voice>() {
   1682             @Override
   1683             public Voice run(ITextToSpeechService service) throws RemoteException {
   1684                 String voiceName = mParams.getString(Engine.KEY_PARAM_VOICE_NAME, "");
   1685                 if (TextUtils.isEmpty(voiceName)) {
   1686                     return null;
   1687                 }
   1688                 return getVoice(service, voiceName);
   1689             }
   1690         }, null, "getVoice");
   1691     }
   1692 
   1693 
   1694     /**
   1695      * Returns a Voice instance of the voice with the given voice name.
   1696      *
   1697      * @return Voice instance with the given voice name, or {@code null} if not set or on error.
   1698      *
   1699      * @see Voice
   1700      */
   1701     private Voice getVoice(ITextToSpeechService service, String voiceName) throws RemoteException {
   1702         List<Voice> voices = service.getVoices();
   1703         if (voices == null) {
   1704             Log.w(TAG, "getVoices returned null");
   1705             return null;
   1706         }
   1707         for (Voice voice : voices) {
   1708             if (voice.getName().equals(voiceName)) {
   1709                 return voice;
   1710             }
   1711         }
   1712         Log.w(TAG, "Could not find voice " + voiceName + " in voice list");
   1713         return null;
   1714     }
   1715 
   1716     /**
   1717      * Returns a Voice instance that's the default voice for the default Text-to-speech language.
   1718      * @return The default voice instance for the default language, or {@code null} if not set or
   1719      *     on error.
   1720      */
   1721     public Voice getDefaultVoice() {
   1722         return runAction(new Action<Voice>() {
   1723             @Override
   1724             public Voice run(ITextToSpeechService service) throws RemoteException {
   1725 
   1726                 String[] defaultLanguage = service.getClientDefaultLanguage();
   1727 
   1728                 if (defaultLanguage == null || defaultLanguage.length == 0) {
   1729                     Log.e(TAG, "service.getClientDefaultLanguage() returned empty array");
   1730                     return null;
   1731                 }
   1732                 String language = defaultLanguage[0];
   1733                 String country = (defaultLanguage.length > 1) ? defaultLanguage[1] : "";
   1734                 String variant = (defaultLanguage.length > 2) ? defaultLanguage[2] : "";
   1735 
   1736                 // Sanitize the locale using isLanguageAvailable.
   1737                 int result = service.isLanguageAvailable(language, country, variant);
   1738                 if (result < LANG_AVAILABLE) {
   1739                     // The default language is not supported.
   1740                     return null;
   1741                 }
   1742 
   1743                 // Get the default voice name
   1744                 String voiceName = service.getDefaultVoiceNameFor(language, country, variant);
   1745                 if (TextUtils.isEmpty(voiceName)) {
   1746                     return null;
   1747                 }
   1748 
   1749                 // Find it
   1750                 List<Voice> voices = service.getVoices();
   1751                 if (voices == null) {
   1752                     return null;
   1753                 }
   1754                 for (Voice voice : voices) {
   1755                     if (voice.getName().equals(voiceName)) {
   1756                         return voice;
   1757                     }
   1758                 }
   1759                 return null;
   1760             }
   1761         }, null, "getDefaultVoice");
   1762     }
   1763 
   1764 
   1765 
   1766     /**
   1767      * Checks if the specified language as represented by the Locale is available and supported.
   1768      *
   1769      * @param loc The Locale describing the language to be used.
   1770      *
   1771      * @return Code indicating the support status for the locale. See {@link #LANG_AVAILABLE},
   1772      *         {@link #LANG_COUNTRY_AVAILABLE}, {@link #LANG_COUNTRY_VAR_AVAILABLE},
   1773      *         {@link #LANG_MISSING_DATA} and {@link #LANG_NOT_SUPPORTED}.
   1774      */
   1775     public int isLanguageAvailable(final Locale loc) {
   1776         return runAction(new Action<Integer>() {
   1777             @Override
   1778             public Integer run(ITextToSpeechService service) throws RemoteException {
   1779                 String language = null, country = null;
   1780 
   1781                 try {
   1782                     language = loc.getISO3Language();
   1783                 } catch (MissingResourceException e) {
   1784                     Log.w(TAG, "Couldn't retrieve ISO 639-2/T language code for locale: " + loc, e);
   1785                     return LANG_NOT_SUPPORTED;
   1786                 }
   1787 
   1788                 try {
   1789                     country = loc.getISO3Country();
   1790                 } catch (MissingResourceException e) {
   1791                     Log.w(TAG, "Couldn't retrieve ISO 3166 country code for locale: " + loc, e);
   1792                     return LANG_NOT_SUPPORTED;
   1793                 }
   1794 
   1795                 return service.isLanguageAvailable(language, country, loc.getVariant());
   1796             }
   1797         }, LANG_NOT_SUPPORTED, "isLanguageAvailable");
   1798     }
   1799 
   1800     /**
   1801      * Synthesizes the given text to a file using the specified parameters.
   1802      * This method is asynchronous, i.e. the method just adds the request to the queue of TTS
   1803      * requests and then returns. The synthesis might not have finished (or even started!) at the
   1804      * time when this method returns. In order to reliably detect errors during synthesis,
   1805      * we recommend setting an utterance progress listener (see
   1806      * {@link #setOnUtteranceProgressListener}).
   1807      *
   1808      * @param text The text that should be synthesized. No longer than
   1809      *            {@link #getMaxSpeechInputLength()} characters.
   1810      * @param params Parameters for the request. Can be null.
   1811      *            Engine specific parameters may be passed in but the parameter keys
   1812      *            must be prefixed by the name of the engine they are intended for. For example
   1813      *            the keys "com.svox.pico_foo" and "com.svox.pico:bar" will be passed to the
   1814      *            engine named "com.svox.pico" if it is being used.
   1815      * @param file File to write the generated audio data to.
   1816      * @param utteranceId An unique identifier for this request.
   1817      * @return {@link #ERROR} or {@link #SUCCESS} of <b>queuing</b> the synthesizeToFile operation.
   1818      */
   1819     public int synthesizeToFile(final CharSequence text, final Bundle params,
   1820             final File file, final String utteranceId) {
   1821         return runAction(new Action<Integer>() {
   1822             @Override
   1823             public Integer run(ITextToSpeechService service) throws RemoteException {
   1824                 ParcelFileDescriptor fileDescriptor;
   1825                 int returnValue;
   1826                 try {
   1827                     if(file.exists() && !file.canWrite()) {
   1828                         Log.e(TAG, "Can't write to " + file);
   1829                         return ERROR;
   1830                     }
   1831                     fileDescriptor = ParcelFileDescriptor.open(file,
   1832                             ParcelFileDescriptor.MODE_WRITE_ONLY |
   1833                             ParcelFileDescriptor.MODE_CREATE |
   1834                             ParcelFileDescriptor.MODE_TRUNCATE);
   1835                     returnValue = service.synthesizeToFileDescriptor(getCallerIdentity(), text,
   1836                             fileDescriptor, getParams(params), utteranceId);
   1837                     fileDescriptor.close();
   1838                     return returnValue;
   1839                 } catch (FileNotFoundException e) {
   1840                     Log.e(TAG, "Opening file " + file + " failed", e);
   1841                     return ERROR;
   1842                 } catch (IOException e) {
   1843                     Log.e(TAG, "Closing file " + file + " failed", e);
   1844                     return ERROR;
   1845                 }
   1846             }
   1847         }, ERROR, "synthesizeToFile");
   1848     }
   1849 
   1850     /**
   1851      * Synthesizes the given text to a file using the specified parameters.
   1852      * This method is asynchronous, i.e. the method just adds the request to the queue of TTS
   1853      * requests and then returns. The synthesis might not have finished (or even started!) at the
   1854      * time when this method returns. In order to reliably detect errors during synthesis,
   1855      * we recommend setting an utterance progress listener (see
   1856      * {@link #setOnUtteranceProgressListener}) and using the
   1857      * {@link Engine#KEY_PARAM_UTTERANCE_ID} parameter.
   1858      *
   1859      * @param text The text that should be synthesized. No longer than
   1860      *            {@link #getMaxSpeechInputLength()} characters.
   1861      * @param params Parameters for the request. Can be null.
   1862      *            Supported parameter names:
   1863      *            {@link Engine#KEY_PARAM_UTTERANCE_ID}.
   1864      *            Engine specific parameters may be passed in but the parameter keys
   1865      *            must be prefixed by the name of the engine they are intended for. For example
   1866      *            the keys "com.svox.pico_foo" and "com.svox.pico:bar" will be passed to the
   1867      *            engine named "com.svox.pico" if it is being used.
   1868      * @param filename Absolute file filename to write the generated audio data to.It should be
   1869      *            something like "/sdcard/myappsounds/mysound.wav".
   1870      *
   1871      * @return {@link #ERROR} or {@link #SUCCESS} of <b>queuing</b> the synthesizeToFile operation.
   1872      * @deprecated As of API level 21, replaced by
   1873      *         {@link #synthesizeToFile(CharSequence, Bundle, File, String)}.
   1874      */
   1875     @Deprecated
   1876     public int synthesizeToFile(final String text, final HashMap<String, String> params,
   1877             final String filename) {
   1878         return synthesizeToFile(text, convertParamsHashMaptoBundle(params),
   1879                 new File(filename), params.get(Engine.KEY_PARAM_UTTERANCE_ID));
   1880     }
   1881 
   1882     private Bundle convertParamsHashMaptoBundle(HashMap<String, String> params) {
   1883         if (params != null && !params.isEmpty()) {
   1884             Bundle bundle = new Bundle();
   1885             copyIntParam(bundle, params, Engine.KEY_PARAM_STREAM);
   1886             copyIntParam(bundle, params, Engine.KEY_PARAM_SESSION_ID);
   1887             copyStringParam(bundle, params, Engine.KEY_PARAM_UTTERANCE_ID);
   1888             copyFloatParam(bundle, params, Engine.KEY_PARAM_VOLUME);
   1889             copyFloatParam(bundle, params, Engine.KEY_PARAM_PAN);
   1890 
   1891             // Copy feature strings defined by the framework.
   1892             copyStringParam(bundle, params, Engine.KEY_FEATURE_NETWORK_SYNTHESIS);
   1893             copyStringParam(bundle, params, Engine.KEY_FEATURE_EMBEDDED_SYNTHESIS);
   1894             copyIntParam(bundle, params, Engine.KEY_FEATURE_NETWORK_TIMEOUT_MS);
   1895             copyIntParam(bundle, params, Engine.KEY_FEATURE_NETWORK_RETRIES_COUNT);
   1896 
   1897             // Copy over all parameters that start with the name of the
   1898             // engine that we are currently connected to. The engine is
   1899             // free to interpret them as it chooses.
   1900             if (!TextUtils.isEmpty(mCurrentEngine)) {
   1901                 for (Map.Entry<String, String> entry : params.entrySet()) {
   1902                     final String key = entry.getKey();
   1903                     if (key != null && key.startsWith(mCurrentEngine)) {
   1904                         bundle.putString(key, entry.getValue());
   1905                     }
   1906                 }
   1907             }
   1908 
   1909             return bundle;
   1910         }
   1911         return null;
   1912     }
   1913 
   1914     private Bundle getParams(Bundle params) {
   1915         if (params != null && !params.isEmpty()) {
   1916             Bundle bundle = new Bundle(mParams);
   1917             bundle.putAll(params);
   1918 
   1919             verifyIntegerBundleParam(bundle, Engine.KEY_PARAM_STREAM);
   1920             verifyIntegerBundleParam(bundle, Engine.KEY_PARAM_SESSION_ID);
   1921             verifyStringBundleParam(bundle, Engine.KEY_PARAM_UTTERANCE_ID);
   1922             verifyFloatBundleParam(bundle, Engine.KEY_PARAM_VOLUME);
   1923             verifyFloatBundleParam(bundle, Engine.KEY_PARAM_PAN);
   1924 
   1925             // Copy feature strings defined by the framework.
   1926             verifyBooleanBundleParam(bundle, Engine.KEY_FEATURE_NETWORK_SYNTHESIS);
   1927             verifyBooleanBundleParam(bundle, Engine.KEY_FEATURE_EMBEDDED_SYNTHESIS);
   1928             verifyIntegerBundleParam(bundle, Engine.KEY_FEATURE_NETWORK_TIMEOUT_MS);
   1929             verifyIntegerBundleParam(bundle, Engine.KEY_FEATURE_NETWORK_RETRIES_COUNT);
   1930 
   1931             return bundle;
   1932         } else {
   1933             return mParams;
   1934         }
   1935     }
   1936 
   1937     private static boolean verifyIntegerBundleParam(Bundle bundle, String key) {
   1938         if (bundle.containsKey(key)) {
   1939             if (!(bundle.get(key) instanceof Integer ||
   1940                     bundle.get(key) instanceof Long)) {
   1941                 bundle.remove(key);
   1942                 Log.w(TAG, "Synthesis request paramter " + key + " containst value "
   1943                         + " with invalid type. Should be an Integer or a Long");
   1944                 return false;
   1945             }
   1946         }
   1947         return true;
   1948     }
   1949 
   1950     private static boolean verifyStringBundleParam(Bundle bundle, String key) {
   1951         if (bundle.containsKey(key)) {
   1952             if (!(bundle.get(key) instanceof String)) {
   1953                 bundle.remove(key);
   1954                 Log.w(TAG, "Synthesis request paramter " + key + " containst value "
   1955                         + " with invalid type. Should be a String");
   1956                 return false;
   1957             }
   1958         }
   1959         return true;
   1960     }
   1961 
   1962     private static boolean verifyBooleanBundleParam(Bundle bundle, String key) {
   1963         if (bundle.containsKey(key)) {
   1964             if (!(bundle.get(key) instanceof Boolean ||
   1965                     bundle.get(key) instanceof String)) {
   1966                 bundle.remove(key);
   1967                 Log.w(TAG, "Synthesis request paramter " + key + " containst value "
   1968                         + " with invalid type. Should be a Boolean or String");
   1969                 return false;
   1970             }
   1971         }
   1972         return true;
   1973     }
   1974 
   1975 
   1976     private static boolean verifyFloatBundleParam(Bundle bundle, String key) {
   1977         if (bundle.containsKey(key)) {
   1978             if (!(bundle.get(key) instanceof Float ||
   1979                     bundle.get(key) instanceof Double)) {
   1980                 bundle.remove(key);
   1981                 Log.w(TAG, "Synthesis request paramter " + key + " containst value "
   1982                         + " with invalid type. Should be a Float or a Double");
   1983                 return false;
   1984             }
   1985         }
   1986         return true;
   1987     }
   1988 
   1989     private void copyStringParam(Bundle bundle, HashMap<String, String> params, String key) {
   1990         String value = params.get(key);
   1991         if (value != null) {
   1992             bundle.putString(key, value);
   1993         }
   1994     }
   1995 
   1996     private void copyIntParam(Bundle bundle, HashMap<String, String> params, String key) {
   1997         String valueString = params.get(key);
   1998         if (!TextUtils.isEmpty(valueString)) {
   1999             try {
   2000                 int value = Integer.parseInt(valueString);
   2001                 bundle.putInt(key, value);
   2002             } catch (NumberFormatException ex) {
   2003                 // don't set the value in the bundle
   2004             }
   2005         }
   2006     }
   2007 
   2008     private void copyFloatParam(Bundle bundle, HashMap<String, String> params, String key) {
   2009         String valueString = params.get(key);
   2010         if (!TextUtils.isEmpty(valueString)) {
   2011             try {
   2012                 float value = Float.parseFloat(valueString);
   2013                 bundle.putFloat(key, value);
   2014             } catch (NumberFormatException ex) {
   2015                 // don't set the value in the bundle
   2016             }
   2017         }
   2018     }
   2019 
   2020     /**
   2021      * Sets the listener that will be notified when synthesis of an utterance completes.
   2022      *
   2023      * @param listener The listener to use.
   2024      *
   2025      * @return {@link #ERROR} or {@link #SUCCESS}.
   2026      *
   2027      * @deprecated Use {@link #setOnUtteranceProgressListener(UtteranceProgressListener)}
   2028      *        instead.
   2029      */
   2030     @Deprecated
   2031     public int setOnUtteranceCompletedListener(final OnUtteranceCompletedListener listener) {
   2032         mUtteranceProgressListener = UtteranceProgressListener.from(listener);
   2033         return TextToSpeech.SUCCESS;
   2034     }
   2035 
   2036     /**
   2037      * Sets the listener that will be notified of various events related to the
   2038      * synthesis of a given utterance.
   2039      *
   2040      * See {@link UtteranceProgressListener} and
   2041      * {@link TextToSpeech.Engine#KEY_PARAM_UTTERANCE_ID}.
   2042      *
   2043      * @param listener the listener to use.
   2044      * @return {@link #ERROR} or {@link #SUCCESS}
   2045      */
   2046     public int setOnUtteranceProgressListener(UtteranceProgressListener listener) {
   2047         mUtteranceProgressListener = listener;
   2048         return TextToSpeech.SUCCESS;
   2049     }
   2050 
   2051     /**
   2052      * Sets the TTS engine to use.
   2053      *
   2054      * @deprecated This doesn't inform callers when the TTS engine has been
   2055      *        initialized. {@link #TextToSpeech(Context, OnInitListener, String)}
   2056      *        can be used with the appropriate engine name. Also, there is no
   2057      *        guarantee that the engine specified will be loaded. If it isn't
   2058      *        installed or disabled, the user / system wide defaults will apply.
   2059      *
   2060      * @param enginePackageName The package name for the synthesis engine (e.g. "com.svox.pico")
   2061      *
   2062      * @return {@link #ERROR} or {@link #SUCCESS}.
   2063      */
   2064     @Deprecated
   2065     public int setEngineByPackageName(String enginePackageName) {
   2066         mRequestedEngine = enginePackageName;
   2067         return initTts();
   2068     }
   2069 
   2070     /**
   2071      * Gets the package name of the default speech synthesis engine.
   2072      *
   2073      * @return Package name of the TTS engine that the user has chosen
   2074      *        as their default.
   2075      */
   2076     public String getDefaultEngine() {
   2077         return mEnginesHelper.getDefaultEngine();
   2078     }
   2079 
   2080     /**
   2081      * Checks whether the user's settings should override settings requested
   2082      * by the calling application. As of the Ice cream sandwich release,
   2083      * user settings never forcibly override the app's settings.
   2084      */
   2085     @Deprecated
   2086     public boolean areDefaultsEnforced() {
   2087         return false;
   2088     }
   2089 
   2090     /**
   2091      * Gets a list of all installed TTS engines.
   2092      *
   2093      * @return A list of engine info objects. The list can be empty, but never {@code null}.
   2094      */
   2095     public List<EngineInfo> getEngines() {
   2096         return mEnginesHelper.getEngines();
   2097     }
   2098 
   2099     private class Connection implements ServiceConnection {
   2100         private ITextToSpeechService mService;
   2101 
   2102         private SetupConnectionAsyncTask mOnSetupConnectionAsyncTask;
   2103 
   2104         private boolean mEstablished;
   2105 
   2106         private final ITextToSpeechCallback.Stub mCallback =
   2107                 new ITextToSpeechCallback.Stub() {
   2108                     public void onStop(String utteranceId, boolean isStarted)
   2109                             throws RemoteException {
   2110                         UtteranceProgressListener listener = mUtteranceProgressListener;
   2111                         if (listener != null) {
   2112                             listener.onStop(utteranceId, isStarted);
   2113                         }
   2114                     };
   2115 
   2116                     @Override
   2117                     public void onSuccess(String utteranceId) {
   2118                         UtteranceProgressListener listener = mUtteranceProgressListener;
   2119                         if (listener != null) {
   2120                             listener.onDone(utteranceId);
   2121                         }
   2122                     }
   2123 
   2124                     @Override
   2125                     public void onError(String utteranceId, int errorCode) {
   2126                         UtteranceProgressListener listener = mUtteranceProgressListener;
   2127                         if (listener != null) {
   2128                             listener.onError(utteranceId);
   2129                         }
   2130                     }
   2131 
   2132                     @Override
   2133                     public void onStart(String utteranceId) {
   2134                         UtteranceProgressListener listener = mUtteranceProgressListener;
   2135                         if (listener != null) {
   2136                             listener.onStart(utteranceId);
   2137                         }
   2138                     }
   2139 
   2140                     @Override
   2141                     public void onBeginSynthesis(
   2142                             String utteranceId,
   2143                             int sampleRateInHz,
   2144                             int audioFormat,
   2145                             int channelCount) {
   2146                         UtteranceProgressListener listener = mUtteranceProgressListener;
   2147                         if (listener != null) {
   2148                             listener.onBeginSynthesis(
   2149                                     utteranceId, sampleRateInHz, audioFormat, channelCount);
   2150                         }
   2151                     }
   2152 
   2153                     @Override
   2154                     public void onAudioAvailable(String utteranceId, byte[] audio) {
   2155                         UtteranceProgressListener listener = mUtteranceProgressListener;
   2156                         if (listener != null) {
   2157                             listener.onAudioAvailable(utteranceId, audio);
   2158                         }
   2159                     }
   2160 
   2161                     @Override
   2162                     public void onRangeStart(String utteranceId, int start, int end, int frame) {
   2163                         UtteranceProgressListener listener = mUtteranceProgressListener;
   2164                         if (listener != null) {
   2165                             listener.onRangeStart(utteranceId, start, end, frame);
   2166                         }
   2167                     }
   2168                 };
   2169 
   2170         private class SetupConnectionAsyncTask extends AsyncTask<Void, Void, Integer> {
   2171             private final ComponentName mName;
   2172 
   2173             public SetupConnectionAsyncTask(ComponentName name) {
   2174                 mName = name;
   2175             }
   2176 
   2177             @Override
   2178             protected Integer doInBackground(Void... params) {
   2179                 synchronized(mStartLock) {
   2180                     if (isCancelled()) {
   2181                         return null;
   2182                     }
   2183 
   2184                     try {
   2185                         mService.setCallback(getCallerIdentity(), mCallback);
   2186 
   2187                         if (mParams.getString(Engine.KEY_PARAM_LANGUAGE) == null) {
   2188                             String[] defaultLanguage = mService.getClientDefaultLanguage();
   2189                             mParams.putString(Engine.KEY_PARAM_LANGUAGE, defaultLanguage[0]);
   2190                             mParams.putString(Engine.KEY_PARAM_COUNTRY, defaultLanguage[1]);
   2191                             mParams.putString(Engine.KEY_PARAM_VARIANT, defaultLanguage[2]);
   2192 
   2193                             // Get the default voice for the locale.
   2194                             String defaultVoiceName = mService.getDefaultVoiceNameFor(
   2195                                 defaultLanguage[0], defaultLanguage[1], defaultLanguage[2]);
   2196                             mParams.putString(Engine.KEY_PARAM_VOICE_NAME, defaultVoiceName);
   2197                         }
   2198 
   2199                         Log.i(TAG, "Set up connection to " + mName);
   2200                         return SUCCESS;
   2201                     } catch (RemoteException re) {
   2202                         Log.e(TAG, "Error connecting to service, setCallback() failed");
   2203                         return ERROR;
   2204                     }
   2205                 }
   2206             }
   2207 
   2208             @Override
   2209             protected void onPostExecute(Integer result) {
   2210                 synchronized(mStartLock) {
   2211                     if (mOnSetupConnectionAsyncTask == this) {
   2212                         mOnSetupConnectionAsyncTask = null;
   2213                     }
   2214                     mEstablished = true;
   2215                     dispatchOnInit(result);
   2216                 }
   2217             }
   2218         }
   2219 
   2220         @Override
   2221         public void onServiceConnected(ComponentName name, IBinder service) {
   2222             synchronized(mStartLock) {
   2223                 mConnectingServiceConnection = null;
   2224 
   2225                 Log.i(TAG, "Connected to " + name);
   2226 
   2227                 if (mOnSetupConnectionAsyncTask != null) {
   2228                     mOnSetupConnectionAsyncTask.cancel(false);
   2229                 }
   2230 
   2231                 mService = ITextToSpeechService.Stub.asInterface(service);
   2232                 mServiceConnection = Connection.this;
   2233 
   2234                 mEstablished = false;
   2235                 mOnSetupConnectionAsyncTask = new SetupConnectionAsyncTask(name);
   2236                 mOnSetupConnectionAsyncTask.execute();
   2237             }
   2238         }
   2239 
   2240         public IBinder getCallerIdentity() {
   2241             return mCallback;
   2242         }
   2243 
   2244         /**
   2245          * Clear connection related fields and cancel mOnServiceConnectedAsyncTask if set.
   2246          *
   2247          * @return true if we cancel mOnSetupConnectionAsyncTask in progress.
   2248          */
   2249         private boolean clearServiceConnection() {
   2250             synchronized(mStartLock) {
   2251                 boolean result = false;
   2252                 if (mOnSetupConnectionAsyncTask != null) {
   2253                     result = mOnSetupConnectionAsyncTask.cancel(false);
   2254                     mOnSetupConnectionAsyncTask = null;
   2255                 }
   2256 
   2257                 mService = null;
   2258                 // If this is the active connection, clear it
   2259                 if (mServiceConnection == this) {
   2260                     mServiceConnection = null;
   2261                 }
   2262                 return result;
   2263             }
   2264         }
   2265 
   2266         @Override
   2267         public void onServiceDisconnected(ComponentName name) {
   2268             Log.i(TAG, "Asked to disconnect from " + name);
   2269             if (clearServiceConnection()) {
   2270                 /* We need to protect against a rare case where engine
   2271                  * dies just after successful connection - and we process onServiceDisconnected
   2272                  * before OnServiceConnectedAsyncTask.onPostExecute. onServiceDisconnected cancels
   2273                  * OnServiceConnectedAsyncTask.onPostExecute and we don't call dispatchOnInit
   2274                  * with ERROR as argument.
   2275                  */
   2276                 dispatchOnInit(ERROR);
   2277             }
   2278         }
   2279 
   2280         public void disconnect() {
   2281             mContext.unbindService(this);
   2282             clearServiceConnection();
   2283         }
   2284 
   2285         public boolean isEstablished() {
   2286             return mService != null && mEstablished;
   2287         }
   2288 
   2289         public <R> R runAction(Action<R> action, R errorResult, String method,
   2290                 boolean reconnect, boolean onlyEstablishedConnection) {
   2291             synchronized (mStartLock) {
   2292                 try {
   2293                     if (mService == null) {
   2294                         Log.w(TAG, method + " failed: not connected to TTS engine");
   2295                         return errorResult;
   2296                     }
   2297                     if (onlyEstablishedConnection && !isEstablished()) {
   2298                         Log.w(TAG, method + " failed: TTS engine connection not fully set up");
   2299                         return errorResult;
   2300                     }
   2301                     return action.run(mService);
   2302                 } catch (RemoteException ex) {
   2303                     Log.e(TAG, method + " failed", ex);
   2304                     if (reconnect) {
   2305                         disconnect();
   2306                         initTts();
   2307                     }
   2308                     return errorResult;
   2309                 }
   2310             }
   2311         }
   2312     }
   2313 
   2314     private interface Action<R> {
   2315         R run(ITextToSpeechService service) throws RemoteException;
   2316     }
   2317 
   2318     /**
   2319      * Information about an installed text-to-speech engine.
   2320      *
   2321      * @see TextToSpeech#getEngines
   2322      */
   2323     public static class EngineInfo {
   2324         /**
   2325          * Engine package name..
   2326          */
   2327         public String name;
   2328         /**
   2329          * Localized label for the engine.
   2330          */
   2331         public String label;
   2332         /**
   2333          * Icon for the engine.
   2334          */
   2335         public int icon;
   2336         /**
   2337          * Whether this engine is a part of the system
   2338          * image.
   2339          *
   2340          * @hide
   2341          */
   2342         public boolean system;
   2343         /**
   2344          * The priority the engine declares for the the intent filter
   2345          * {@code android.intent.action.TTS_SERVICE}
   2346          *
   2347          * @hide
   2348          */
   2349         public int priority;
   2350 
   2351         @Override
   2352         public String toString() {
   2353             return "EngineInfo{name=" + name + "}";
   2354         }
   2355 
   2356     }
   2357 
   2358     /**
   2359      * Limit of length of input string passed to speak and synthesizeToFile.
   2360      *
   2361      * @see #speak
   2362      * @see #synthesizeToFile
   2363      */
   2364     public static int getMaxSpeechInputLength() {
   2365         return 4000;
   2366     }
   2367 }
   2368