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