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