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