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.AudioManager;
     26 import android.net.Uri;
     27 import android.os.AsyncTask;
     28 import android.os.Bundle;
     29 import android.os.IBinder;
     30 import android.os.ParcelFileDescriptor;
     31 import android.os.RemoteException;
     32 import android.provider.Settings;
     33 import android.text.TextUtils;
     34 import android.util.Log;
     35 
     36 import java.io.File;
     37 import java.io.FileNotFoundException;
     38 import java.io.IOException;
     39 import java.util.Collections;
     40 import java.util.HashMap;
     41 import java.util.HashSet;
     42 import java.util.List;
     43 import java.util.Locale;
     44 import java.util.Map;
     45 import java.util.MissingResourceException;
     46 import java.util.Set;
     47 
     48 /**
     49  *
     50  * Synthesizes speech from text for immediate playback or to create a sound file.
     51  * <p>A TextToSpeech instance can only be used to synthesize text once it has completed its
     52  * initialization. Implement the {@link TextToSpeech.OnInitListener} to be
     53  * notified of the completion of the initialization.<br>
     54  * When you are done using the TextToSpeech instance, call the {@link #shutdown()} method
     55  * to release the native resources used by the TextToSpeech engine.
     56  *
     57  */
     58 public class TextToSpeech {
     59 
     60     private static final String TAG = "TextToSpeech";
     61 
     62     /**
     63      * Denotes a successful operation.
     64      */
     65     public static final int SUCCESS = 0;
     66     /**
     67      * Denotes a generic operation failure.
     68      */
     69     public static final int ERROR = -1;
     70 
     71     /**
     72      * Queue mode where all entries in the playback queue (media to be played
     73      * and text to be synthesized) are dropped and replaced by the new entry.
     74      * Queues are flushed with respect to a given calling app. Entries in the queue
     75      * from other callees are not discarded.
     76      */
     77     public static final int QUEUE_FLUSH = 0;
     78     /**
     79      * Queue mode where the new entry is added at the end of the playback queue.
     80      */
     81     public static final int QUEUE_ADD = 1;
     82     /**
     83      * Queue mode where the entire playback queue is purged. This is different
     84      * from {@link #QUEUE_FLUSH} in that all entries are purged, not just entries
     85      * from a given caller.
     86      *
     87      * @hide
     88      */
     89     static final int QUEUE_DESTROY = 2;
     90 
     91     /**
     92      * Denotes the language is available exactly as specified by the locale.
     93      */
     94     public static final int LANG_COUNTRY_VAR_AVAILABLE = 2;
     95 
     96     /**
     97      * Denotes the language is available for the language and country specified
     98      * by the locale, but not the variant.
     99      */
    100     public static final int LANG_COUNTRY_AVAILABLE = 1;
    101 
    102     /**
    103      * Denotes the language is available for the language by the locale,
    104      * but not the country and variant.
    105      */
    106     public static final int LANG_AVAILABLE = 0;
    107 
    108     /**
    109      * Denotes the language data is missing.
    110      */
    111     public static final int LANG_MISSING_DATA = -1;
    112 
    113     /**
    114      * Denotes the language is not supported.
    115      */
    116     public static final int LANG_NOT_SUPPORTED = -2;
    117 
    118     /**
    119      * Broadcast Action: The TextToSpeech synthesizer has completed processing
    120      * of all the text in the speech queue.
    121      *
    122      * Note that this notifies callers when the <b>engine</b> has finished has
    123      * processing text data. Audio playback might not have completed (or even started)
    124      * at this point. If you wish to be notified when this happens, see
    125      * {@link OnUtteranceCompletedListener}.
    126      */
    127     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
    128     public static final String ACTION_TTS_QUEUE_PROCESSING_COMPLETED =
    129             "android.speech.tts.TTS_QUEUE_PROCESSING_COMPLETED";
    130 
    131     /**
    132      * Interface definition of a callback to be invoked indicating the completion of the
    133      * TextToSpeech engine initialization.
    134      */
    135     public interface OnInitListener {
    136         /**
    137          * Called to signal the completion of the TextToSpeech engine initialization.
    138          *
    139          * @param status {@link TextToSpeech#SUCCESS} or {@link TextToSpeech#ERROR}.
    140          */
    141         public void onInit(int status);
    142     }
    143 
    144     /**
    145      * Listener that will be called when the TTS service has
    146      * completed synthesizing an utterance. This is only called if the utterance
    147      * has an utterance ID (see {@link TextToSpeech.Engine#KEY_PARAM_UTTERANCE_ID}).
    148      *
    149      * @deprecated Use {@link UtteranceProgressListener} instead.
    150      */
    151     @Deprecated
    152     public interface OnUtteranceCompletedListener {
    153         /**
    154          * Called when an utterance has been synthesized.
    155          *
    156          * @param utteranceId the identifier of the utterance.
    157          */
    158         public void onUtteranceCompleted(String utteranceId);
    159     }
    160 
    161     /**
    162      * Constants and parameter names for controlling text-to-speech. These include:
    163      *
    164      * <ul>
    165      *     <li>
    166      *         Intents to ask engine to install data or check its data and
    167      *         extras for a TTS engine's check data activity.
    168      *     </li>
    169      *     <li>
    170      *         Keys for the parameters passed with speak commands, e.g.
    171      *         {@link Engine#KEY_PARAM_UTTERANCE_ID}, {@link Engine#KEY_PARAM_STREAM}.
    172      *     </li>
    173      *     <li>
    174      *         A list of feature strings that engines might support, e.g
    175      *         {@link Engine#KEY_FEATURE_NETWORK_SYNTHESIS}). These values may be passed in to
    176      *         {@link TextToSpeech#speak} and {@link TextToSpeech#synthesizeToFile} to modify
    177      *         engine behaviour. The engine can be queried for the set of features it supports
    178      *         through {@link TextToSpeech#getFeatures(java.util.Locale)}.
    179      *     </li>
    180      * </ul>
    181      */
    182     public class Engine {
    183 
    184         /**
    185          * Default speech rate.
    186          * @hide
    187          */
    188         public static final int DEFAULT_RATE = 100;
    189 
    190         /**
    191          * Default pitch.
    192          * @hide
    193          */
    194         public static final int DEFAULT_PITCH = 100;
    195 
    196         /**
    197          * Default volume.
    198          * @hide
    199          */
    200         public static final float DEFAULT_VOLUME = 1.0f;
    201 
    202         /**
    203          * Default pan (centered).
    204          * @hide
    205          */
    206         public static final float DEFAULT_PAN = 0.0f;
    207 
    208         /**
    209          * Default value for {@link Settings.Secure#TTS_USE_DEFAULTS}.
    210          * @hide
    211          */
    212         public static final int USE_DEFAULTS = 0; // false
    213 
    214         /**
    215          * Package name of the default TTS engine.
    216          *
    217          * @hide
    218          * @deprecated No longer in use, the default engine is determined by
    219          *         the sort order defined in {@link TtsEngines}. Note that
    220          *         this doesn't "break" anything because there is no guarantee that
    221          *         the engine specified below is installed on a given build, let
    222          *         alone be the default.
    223          */
    224         @Deprecated
    225         public static final String DEFAULT_ENGINE = "com.svox.pico";
    226 
    227         /**
    228          * Default audio stream used when playing synthesized speech.
    229          */
    230         public static final int DEFAULT_STREAM = AudioManager.STREAM_MUSIC;
    231 
    232         /**
    233          * Indicates success when checking the installation status of the resources used by the
    234          * TextToSpeech engine with the {@link #ACTION_CHECK_TTS_DATA} intent.
    235          */
    236         public static final int CHECK_VOICE_DATA_PASS = 1;
    237 
    238         /**
    239          * Indicates failure when checking the installation status of the resources used by the
    240          * TextToSpeech engine with the {@link #ACTION_CHECK_TTS_DATA} intent.
    241          */
    242         public static final int CHECK_VOICE_DATA_FAIL = 0;
    243 
    244         /**
    245          * Indicates erroneous data when checking the installation status of the resources used by
    246          * the TextToSpeech engine with the {@link #ACTION_CHECK_TTS_DATA} intent.
    247          *
    248          * @deprecated Use CHECK_VOICE_DATA_FAIL instead.
    249          */
    250         @Deprecated
    251         public static final int CHECK_VOICE_DATA_BAD_DATA = -1;
    252 
    253         /**
    254          * Indicates missing resources when checking the installation status of the resources used
    255          * by the TextToSpeech engine with the {@link #ACTION_CHECK_TTS_DATA} intent.
    256          *
    257          * @deprecated Use CHECK_VOICE_DATA_FAIL instead.
    258          */
    259         @Deprecated
    260         public static final int CHECK_VOICE_DATA_MISSING_DATA = -2;
    261 
    262         /**
    263          * Indicates missing storage volume when checking the installation status of the resources
    264          * used by the TextToSpeech engine with the {@link #ACTION_CHECK_TTS_DATA} intent.
    265          *
    266          * @deprecated Use CHECK_VOICE_DATA_FAIL instead.
    267          */
    268         @Deprecated
    269         public static final int CHECK_VOICE_DATA_MISSING_VOLUME = -3;
    270 
    271         /**
    272          * Intent for starting a TTS service. Services that handle this intent must
    273          * extend {@link TextToSpeechService}. Normal applications should not use this intent
    274          * directly, instead they should talk to the TTS service using the the methods in this
    275          * class.
    276          */
    277         @SdkConstant(SdkConstantType.SERVICE_ACTION)
    278         public static final String INTENT_ACTION_TTS_SERVICE =
    279                 "android.intent.action.TTS_SERVICE";
    280 
    281         /**
    282          * Name under which a text to speech engine publishes information about itself.
    283          * This meta-data should reference an XML resource containing a
    284          * <code>&lt;{@link android.R.styleable#TextToSpeechEngine tts-engine}&gt;</code>
    285          * tag.
    286          */
    287         public static final String SERVICE_META_DATA = "android.speech.tts";
    288 
    289         // intents to ask engine to install data or check its data
    290         /**
    291          * Activity Action: Triggers the platform TextToSpeech engine to
    292          * start the activity that installs the resource files on the device
    293          * that are required for TTS to be operational. Since the installation
    294          * of the data can be interrupted or declined by the user, the application
    295          * shouldn't expect successful installation upon return from that intent,
    296          * and if need be, should check installation status with
    297          * {@link #ACTION_CHECK_TTS_DATA}.
    298          */
    299         @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
    300         public static final String ACTION_INSTALL_TTS_DATA =
    301                 "android.speech.tts.engine.INSTALL_TTS_DATA";
    302 
    303         /**
    304          * Broadcast Action: broadcast to signal the change in the list of available
    305          * languages or/and their features.
    306          */
    307         @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
    308         public static final String ACTION_TTS_DATA_INSTALLED =
    309                 "android.speech.tts.engine.TTS_DATA_INSTALLED";
    310 
    311         /**
    312          * Activity Action: Starts the activity from the platform TextToSpeech
    313          * engine to verify the proper installation and availability of the
    314          * resource files on the system. Upon completion, the activity will
    315          * return one of the following codes:
    316          * {@link #CHECK_VOICE_DATA_PASS},
    317          * {@link #CHECK_VOICE_DATA_FAIL},
    318          * <p> Moreover, the data received in the activity result will contain the following
    319          * fields:
    320          * <ul>
    321          *   <li>{@link #EXTRA_AVAILABLE_VOICES} which contains an ArrayList<String> of all the
    322          *   available voices. The format of each voice is: lang-COUNTRY-variant where COUNTRY and
    323          *   variant are optional (ie, "eng" or "eng-USA" or "eng-USA-FEMALE").</li>
    324          *   <li>{@link #EXTRA_UNAVAILABLE_VOICES} which contains an ArrayList<String> of all the
    325          *   unavailable voices (ones that user can install). The format of each voice is:
    326          *   lang-COUNTRY-variant where COUNTRY and variant are optional (ie, "eng" or
    327          *   "eng-USA" or "eng-USA-FEMALE").</li>
    328          * </ul>
    329          */
    330         @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
    331         public static final String ACTION_CHECK_TTS_DATA =
    332                 "android.speech.tts.engine.CHECK_TTS_DATA";
    333 
    334         /**
    335          * Activity intent for getting some sample text to use for demonstrating TTS. Specific
    336          * locale have to be requested by passing following extra parameters:
    337          * <ul>
    338          *   <li>language</li>
    339          *   <li>country</li>
    340          *   <li>variant</li>
    341          * </ul>
    342          *
    343          * Upon completion, the activity result may contain the following fields:
    344          * <ul>
    345          *   <li>{@link #EXTRA_SAMPLE_TEXT} which contains an String with sample text.</li>
    346          * </ul>
    347          */
    348         @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
    349         public static final String ACTION_GET_SAMPLE_TEXT =
    350                 "android.speech.tts.engine.GET_SAMPLE_TEXT";
    351 
    352         /**
    353          * Extra information received with the {@link #ACTION_GET_SAMPLE_TEXT} intent result where
    354          * the TextToSpeech engine returns an String with sample text for requested voice
    355          */
    356         public static final String EXTRA_SAMPLE_TEXT = "sampleText";
    357 
    358 
    359         // extras for a TTS engine's check data activity
    360         /**
    361          * Extra information received with the {@link #ACTION_CHECK_TTS_DATA} intent result where
    362          * the TextToSpeech engine returns an ArrayList<String> of all the available voices.
    363          * The format of each voice is: lang-COUNTRY-variant where COUNTRY and variant are
    364          * optional (ie, "eng" or "eng-USA" or "eng-USA-FEMALE").
    365          */
    366         public static final String EXTRA_AVAILABLE_VOICES = "availableVoices";
    367 
    368         /**
    369          * Extra information received with the {@link #ACTION_CHECK_TTS_DATA} intent result where
    370          * the TextToSpeech engine returns an ArrayList<String> of all the unavailable voices.
    371          * The format of each voice is: lang-COUNTRY-variant where COUNTRY and variant are
    372          * optional (ie, "eng" or "eng-USA" or "eng-USA-FEMALE").
    373          */
    374         public static final String EXTRA_UNAVAILABLE_VOICES = "unavailableVoices";
    375 
    376         /**
    377          * Extra information received with the {@link #ACTION_CHECK_TTS_DATA} intent result where
    378          * the TextToSpeech engine specifies the path to its resources.
    379          *
    380          * It may be used by language packages to find out where to put their data.
    381          *
    382          * @deprecated TTS engine implementation detail, this information has no use for
    383          * text-to-speech API client.
    384          */
    385         @Deprecated
    386         public static final String EXTRA_VOICE_DATA_ROOT_DIRECTORY = "dataRoot";
    387 
    388         /**
    389          * Extra information received with the {@link #ACTION_CHECK_TTS_DATA} intent result where
    390          * the TextToSpeech engine specifies the file names of its resources under the
    391          * resource path.
    392          *
    393          * @deprecated TTS engine implementation detail, this information has no use for
    394          * text-to-speech API client.
    395          */
    396         @Deprecated
    397         public static final String EXTRA_VOICE_DATA_FILES = "dataFiles";
    398 
    399         /**
    400          * Extra information received with the {@link #ACTION_CHECK_TTS_DATA} intent result where
    401          * the TextToSpeech engine specifies the locale associated with each resource file.
    402          *
    403          * @deprecated TTS engine implementation detail, this information has no use for
    404          * text-to-speech API client.
    405          */
    406         @Deprecated
    407         public static final String EXTRA_VOICE_DATA_FILES_INFO = "dataFilesInfo";
    408 
    409         /**
    410          * Extra information sent with the {@link #ACTION_CHECK_TTS_DATA} intent where the
    411          * caller indicates to the TextToSpeech engine which specific sets of voice data to
    412          * check for by sending an ArrayList<String> of the voices that are of interest.
    413          * The format of each voice is: lang-COUNTRY-variant where COUNTRY and variant are
    414          * optional (ie, "eng" or "eng-USA" or "eng-USA-FEMALE").
    415          *
    416          * @deprecated Redundant functionality, checking for existence of specific sets of voice
    417          * data can be done on client side.
    418          */
    419         @Deprecated
    420         public static final String EXTRA_CHECK_VOICE_DATA_FOR = "checkVoiceDataFor";
    421 
    422         // extras for a TTS engine's data installation
    423         /**
    424          * Extra information received with the {@link #ACTION_TTS_DATA_INSTALLED} intent result.
    425          * It indicates whether the data files for the synthesis engine were successfully
    426          * installed. The installation was initiated with the  {@link #ACTION_INSTALL_TTS_DATA}
    427          * intent. The possible values for this extra are
    428          * {@link TextToSpeech#SUCCESS} and {@link TextToSpeech#ERROR}.
    429          *
    430          * @deprecated No longer in use. If client ise interested in information about what
    431          * changed, is should send ACTION_CHECK_TTS_DATA intent to discover available voices.
    432          */
    433         @Deprecated
    434         public static final String EXTRA_TTS_DATA_INSTALLED = "dataInstalled";
    435 
    436         // keys for the parameters passed with speak commands. Hidden keys are used internally
    437         // to maintain engine state for each TextToSpeech instance.
    438         /**
    439          * @hide
    440          */
    441         public static final String KEY_PARAM_RATE = "rate";
    442 
    443         /**
    444          * @hide
    445          */
    446         public static final String KEY_PARAM_LANGUAGE = "language";
    447 
    448         /**
    449          * @hide
    450          */
    451         public static final String KEY_PARAM_COUNTRY = "country";
    452 
    453         /**
    454          * @hide
    455          */
    456         public static final String KEY_PARAM_VARIANT = "variant";
    457 
    458         /**
    459          * @hide
    460          */
    461         public static final String KEY_PARAM_ENGINE = "engine";
    462 
    463         /**
    464          * @hide
    465          */
    466         public static final String KEY_PARAM_PITCH = "pitch";
    467 
    468         /**
    469          * Parameter key to specify the audio stream type to be used when speaking text
    470          * or playing back a file. The value should be one of the STREAM_ constants
    471          * defined in {@link AudioManager}.
    472          *
    473          * @see TextToSpeech#speak(String, int, HashMap)
    474          * @see TextToSpeech#playEarcon(String, int, HashMap)
    475          */
    476         public static final String KEY_PARAM_STREAM = "streamType";
    477 
    478         /**
    479          * Parameter key to identify an utterance in the
    480          * {@link TextToSpeech.OnUtteranceCompletedListener} after text has been
    481          * spoken, a file has been played back or a silence duration has elapsed.
    482          *
    483          * @see TextToSpeech#speak(String, int, HashMap)
    484          * @see TextToSpeech#playEarcon(String, int, HashMap)
    485          * @see TextToSpeech#synthesizeToFile(String, HashMap, String)
    486          */
    487         public static final String KEY_PARAM_UTTERANCE_ID = "utteranceId";
    488 
    489         /**
    490          * Parameter key to specify the speech volume relative to the current stream type
    491          * volume used when speaking text. Volume is specified as a float ranging from 0 to 1
    492          * where 0 is silence, and 1 is the maximum volume (the default behavior).
    493          *
    494          * @see TextToSpeech#speak(String, int, HashMap)
    495          * @see TextToSpeech#playEarcon(String, int, HashMap)
    496          */
    497         public static final String KEY_PARAM_VOLUME = "volume";
    498 
    499         /**
    500          * Parameter key to specify how the speech is panned from left to right when speaking text.
    501          * Pan is specified as a float ranging from -1 to +1 where -1 maps to a hard-left pan,
    502          * 0 to center (the default behavior), and +1 to hard-right.
    503          *
    504          * @see TextToSpeech#speak(String, int, HashMap)
    505          * @see TextToSpeech#playEarcon(String, int, HashMap)
    506          */
    507         public static final String KEY_PARAM_PAN = "pan";
    508 
    509         /**
    510          * Feature key for network synthesis. See {@link TextToSpeech#getFeatures(Locale)}
    511          * for a description of how feature keys work. If set (and supported by the engine
    512          * as per {@link TextToSpeech#getFeatures(Locale)}, the engine must
    513          * use network based synthesis.
    514          *
    515          * @see TextToSpeech#speak(String, int, java.util.HashMap)
    516          * @see TextToSpeech#synthesizeToFile(String, java.util.HashMap, String)
    517          * @see TextToSpeech#getFeatures(java.util.Locale)
    518          */
    519         public static final String KEY_FEATURE_NETWORK_SYNTHESIS = "networkTts";
    520 
    521         /**
    522          * Feature key for embedded synthesis. See {@link TextToSpeech#getFeatures(Locale)}
    523          * for a description of how feature keys work. If set and supported by the engine
    524          * as per {@link TextToSpeech#getFeatures(Locale)}, the engine must synthesize
    525          * text on-device (without making network requests).
    526          *
    527          * @see TextToSpeech#speak(String, int, java.util.HashMap)
    528          * @see TextToSpeech#synthesizeToFile(String, java.util.HashMap, String)
    529          * @see TextToSpeech#getFeatures(java.util.Locale)
    530          */
    531         public static final String KEY_FEATURE_EMBEDDED_SYNTHESIS = "embeddedTts";
    532     }
    533 
    534     private final Context mContext;
    535     private Connection mConnectingServiceConnection;
    536     private Connection mServiceConnection;
    537     private OnInitListener mInitListener;
    538     // Written from an unspecified application thread, read from
    539     // a binder thread.
    540     private volatile UtteranceProgressListener mUtteranceProgressListener;
    541     private final Object mStartLock = new Object();
    542 
    543     private String mRequestedEngine;
    544     // Whether to initialize this TTS object with the default engine,
    545     // if the requested engine is not available. Valid only if mRequestedEngine
    546     // is not null. Used only for testing, though potentially useful API wise
    547     // too.
    548     private final boolean mUseFallback;
    549     private final Map<String, Uri> mEarcons;
    550     private final Map<String, Uri> mUtterances;
    551     private final Bundle mParams = new Bundle();
    552     private final TtsEngines mEnginesHelper;
    553     private final String mPackageName;
    554     private volatile String mCurrentEngine = null;
    555 
    556     /**
    557      * The constructor for the TextToSpeech class, using the default TTS engine.
    558      * This will also initialize the associated TextToSpeech engine if it isn't already running.
    559      *
    560      * @param context
    561      *            The context this instance is running in.
    562      * @param listener
    563      *            The {@link TextToSpeech.OnInitListener} that will be called when the
    564      *            TextToSpeech engine has initialized.
    565      */
    566     public TextToSpeech(Context context, OnInitListener listener) {
    567         this(context, listener, null);
    568     }
    569 
    570     /**
    571      * The constructor for the TextToSpeech class, using the given TTS engine.
    572      * This will also initialize the associated TextToSpeech engine if it isn't already running.
    573      *
    574      * @param context
    575      *            The context this instance is running in.
    576      * @param listener
    577      *            The {@link TextToSpeech.OnInitListener} that will be called when the
    578      *            TextToSpeech engine has initialized.
    579      * @param engine Package name of the TTS engine to use.
    580      */
    581     public TextToSpeech(Context context, OnInitListener listener, String engine) {
    582         this(context, listener, engine, null, true);
    583     }
    584 
    585     /**
    586      * Used by the framework to instantiate TextToSpeech objects with a supplied
    587      * package name, instead of using {@link android.content.Context#getPackageName()}
    588      *
    589      * @hide
    590      */
    591     public TextToSpeech(Context context, OnInitListener listener, String engine,
    592             String packageName, boolean useFallback) {
    593         mContext = context;
    594         mInitListener = listener;
    595         mRequestedEngine = engine;
    596         mUseFallback = useFallback;
    597 
    598         mEarcons = new HashMap<String, Uri>();
    599         mUtterances = new HashMap<String, Uri>();
    600         mUtteranceProgressListener = null;
    601 
    602         mEnginesHelper = new TtsEngines(mContext);
    603         if (packageName != null) {
    604             mPackageName = packageName;
    605         } else {
    606             mPackageName = mContext.getPackageName();
    607         }
    608         initTts();
    609     }
    610 
    611     private <R> R runActionNoReconnect(Action<R> action, R errorResult, String method,
    612             boolean onlyEstablishedConnection) {
    613         return runAction(action, errorResult, method, false, onlyEstablishedConnection);
    614     }
    615 
    616     private <R> R runAction(Action<R> action, R errorResult, String method) {
    617         return runAction(action, errorResult, method, true, true);
    618     }
    619 
    620     private <R> R runAction(Action<R> action, R errorResult, String method,
    621             boolean reconnect, boolean onlyEstablishedConnection) {
    622         synchronized (mStartLock) {
    623             if (mServiceConnection == null) {
    624                 Log.w(TAG, method + " failed: not bound to TTS engine");
    625                 return errorResult;
    626             }
    627             return mServiceConnection.runAction(action, errorResult, method, reconnect,
    628                     onlyEstablishedConnection);
    629         }
    630     }
    631 
    632     private int initTts() {
    633         // Step 1: Try connecting to the engine that was requested.
    634         if (mRequestedEngine != null) {
    635             if (mEnginesHelper.isEngineInstalled(mRequestedEngine)) {
    636                 if (connectToEngine(mRequestedEngine)) {
    637                     mCurrentEngine = mRequestedEngine;
    638                     return SUCCESS;
    639                 } else if (!mUseFallback) {
    640                     mCurrentEngine = null;
    641                     dispatchOnInit(ERROR);
    642                     return ERROR;
    643                 }
    644             } else if (!mUseFallback) {
    645                 Log.i(TAG, "Requested engine not installed: " + mRequestedEngine);
    646                 mCurrentEngine = null;
    647                 dispatchOnInit(ERROR);
    648                 return ERROR;
    649             }
    650         }
    651 
    652         // Step 2: Try connecting to the user's default engine.
    653         final String defaultEngine = getDefaultEngine();
    654         if (defaultEngine != null && !defaultEngine.equals(mRequestedEngine)) {
    655             if (connectToEngine(defaultEngine)) {
    656                 mCurrentEngine = defaultEngine;
    657                 return SUCCESS;
    658             }
    659         }
    660 
    661         // Step 3: Try connecting to the highest ranked engine in the
    662         // system.
    663         final String highestRanked = mEnginesHelper.getHighestRankedEngineName();
    664         if (highestRanked != null && !highestRanked.equals(mRequestedEngine) &&
    665                 !highestRanked.equals(defaultEngine)) {
    666             if (connectToEngine(highestRanked)) {
    667                 mCurrentEngine = highestRanked;
    668                 return SUCCESS;
    669             }
    670         }
    671 
    672         // NOTE: The API currently does not allow the caller to query whether
    673         // they are actually connected to any engine. This might fail for various
    674         // reasons like if the user disables all her TTS engines.
    675 
    676         mCurrentEngine = null;
    677         dispatchOnInit(ERROR);
    678         return ERROR;
    679     }
    680 
    681     private boolean connectToEngine(String engine) {
    682         Connection connection = new Connection();
    683         Intent intent = new Intent(Engine.INTENT_ACTION_TTS_SERVICE);
    684         intent.setPackage(engine);
    685         boolean bound = mContext.bindService(intent, connection, Context.BIND_AUTO_CREATE);
    686         if (!bound) {
    687             Log.e(TAG, "Failed to bind to " + engine);
    688             return false;
    689         } else {
    690             Log.i(TAG, "Sucessfully bound to " + engine);
    691             mConnectingServiceConnection = connection;
    692             return true;
    693         }
    694     }
    695 
    696     private void dispatchOnInit(int result) {
    697         synchronized (mStartLock) {
    698             if (mInitListener != null) {
    699                 mInitListener.onInit(result);
    700                 mInitListener = null;
    701             }
    702         }
    703     }
    704 
    705     private IBinder getCallerIdentity() {
    706         return mServiceConnection.getCallerIdentity();
    707     }
    708 
    709     /**
    710      * Releases the resources used by the TextToSpeech engine.
    711      * It is good practice for instance to call this method in the onDestroy() method of an Activity
    712      * so the TextToSpeech engine can be cleanly stopped.
    713      */
    714     public void shutdown() {
    715         // Special case, we are asked to shutdown connection that did finalize its connection.
    716         synchronized (mStartLock) {
    717             if (mConnectingServiceConnection != null) {
    718                 mContext.unbindService(mConnectingServiceConnection);
    719                 mConnectingServiceConnection = null;
    720                 return;
    721             }
    722         }
    723 
    724         // Post connection case
    725         runActionNoReconnect(new Action<Void>() {
    726             @Override
    727             public Void run(ITextToSpeechService service) throws RemoteException {
    728                 service.setCallback(getCallerIdentity(), null);
    729                 service.stop(getCallerIdentity());
    730                 mServiceConnection.disconnect();
    731                 // Context#unbindService does not result in a call to
    732                 // ServiceConnection#onServiceDisconnected. As a result, the
    733                 // service ends up being destroyed (if there are no other open
    734                 // connections to it) but the process lives on and the
    735                 // ServiceConnection continues to refer to the destroyed service.
    736                 //
    737                 // This leads to tons of log spam about SynthThread being dead.
    738                 mServiceConnection = null;
    739                 mCurrentEngine = null;
    740                 return null;
    741             }
    742         }, null, "shutdown", false);
    743     }
    744 
    745     /**
    746      * Adds a mapping between a string of text and a sound resource in a
    747      * package. After a call to this method, subsequent calls to
    748      * {@link #speak(String, int, HashMap)} will play the specified sound resource
    749      * if it is available, or synthesize the text it is missing.
    750      *
    751      * @param text
    752      *            The string of text. Example: <code>"south_south_east"</code>
    753      *
    754      * @param packagename
    755      *            Pass the packagename of the application that contains the
    756      *            resource. If the resource is in your own application (this is
    757      *            the most common case), then put the packagename of your
    758      *            application here.<br/>
    759      *            Example: <b>"com.google.marvin.compass"</b><br/>
    760      *            The packagename can be found in the AndroidManifest.xml of
    761      *            your application.
    762      *            <p>
    763      *            <code>&lt;manifest xmlns:android=&quot;...&quot;
    764      *      package=&quot;<b>com.google.marvin.compass</b>&quot;&gt;</code>
    765      *            </p>
    766      *
    767      * @param resourceId
    768      *            Example: <code>R.raw.south_south_east</code>
    769      *
    770      * @return Code indicating success or failure. See {@link #ERROR} and {@link #SUCCESS}.
    771      */
    772     public int addSpeech(String text, String packagename, int resourceId) {
    773         synchronized (mStartLock) {
    774             mUtterances.put(text, makeResourceUri(packagename, resourceId));
    775             return SUCCESS;
    776         }
    777     }
    778 
    779     /**
    780      * Adds a mapping between a string of text and a sound file. Using this, it
    781      * is possible to add custom pronounciations for a string of text.
    782      * After a call to this method, subsequent calls to {@link #speak(String, int, HashMap)}
    783      * will play the specified sound resource if it is available, or synthesize the text it is
    784      * missing.
    785      *
    786      * @param text
    787      *            The string of text. Example: <code>"south_south_east"</code>
    788      * @param filename
    789      *            The full path to the sound file (for example:
    790      *            "/sdcard/mysounds/hello.wav")
    791      *
    792      * @return Code indicating success or failure. See {@link #ERROR} and {@link #SUCCESS}.
    793      */
    794     public int addSpeech(String text, String filename) {
    795         synchronized (mStartLock) {
    796             mUtterances.put(text, Uri.parse(filename));
    797             return SUCCESS;
    798         }
    799     }
    800 
    801 
    802     /**
    803      * Adds a mapping between a string of text and a sound resource in a
    804      * package. Use this to add custom earcons.
    805      *
    806      * @see #playEarcon(String, int, HashMap)
    807      *
    808      * @param earcon The name of the earcon.
    809      *            Example: <code>"[tick]"</code><br/>
    810      *
    811      * @param packagename
    812      *            the package name of the application that contains the
    813      *            resource. This can for instance be the package name of your own application.
    814      *            Example: <b>"com.google.marvin.compass"</b><br/>
    815      *            The package name can be found in the AndroidManifest.xml of
    816      *            the application containing the resource.
    817      *            <p>
    818      *            <code>&lt;manifest xmlns:android=&quot;...&quot;
    819      *      package=&quot;<b>com.google.marvin.compass</b>&quot;&gt;</code>
    820      *            </p>
    821      *
    822      * @param resourceId
    823      *            Example: <code>R.raw.tick_snd</code>
    824      *
    825      * @return Code indicating success or failure. See {@link #ERROR} and {@link #SUCCESS}.
    826      */
    827     public int addEarcon(String earcon, String packagename, int resourceId) {
    828         synchronized(mStartLock) {
    829             mEarcons.put(earcon, makeResourceUri(packagename, resourceId));
    830             return SUCCESS;
    831         }
    832     }
    833 
    834     /**
    835      * Adds a mapping between a string of text and a sound file.
    836      * Use this to add custom earcons.
    837      *
    838      * @see #playEarcon(String, int, HashMap)
    839      *
    840      * @param earcon
    841      *            The name of the earcon.
    842      *            Example: <code>"[tick]"</code>
    843      * @param filename
    844      *            The full path to the sound file (for example:
    845      *            "/sdcard/mysounds/tick.wav")
    846      *
    847      * @return Code indicating success or failure. See {@link #ERROR} and {@link #SUCCESS}.
    848      */
    849     public int addEarcon(String earcon, String filename) {
    850         synchronized(mStartLock) {
    851             mEarcons.put(earcon, Uri.parse(filename));
    852             return SUCCESS;
    853         }
    854     }
    855 
    856     private Uri makeResourceUri(String packageName, int resourceId) {
    857         return new Uri.Builder()
    858                 .scheme(ContentResolver.SCHEME_ANDROID_RESOURCE)
    859                 .encodedAuthority(packageName)
    860                 .appendEncodedPath(String.valueOf(resourceId))
    861                 .build();
    862     }
    863 
    864     /**
    865      * Speaks the string using the specified queuing strategy and speech parameters.
    866      * This method is asynchronous, i.e. the method just adds the request to the queue of TTS
    867      * requests and then returns. The synthesis might not have finished (or even started!) at the
    868      * time when this method returns. In order to reliably detect errors during synthesis,
    869      * we recommend setting an utterance progress listener (see
    870      * {@link #setOnUtteranceProgressListener}) and using the
    871      * {@link Engine#KEY_PARAM_UTTERANCE_ID} parameter.
    872      *
    873      * @param text The string of text to be spoken. No longer than
    874      *            {@link #getMaxSpeechInputLength()} characters.
    875      * @param queueMode The queuing strategy to use, {@link #QUEUE_ADD} or {@link #QUEUE_FLUSH}.
    876      * @param params Parameters for the request. Can be null.
    877      *            Supported parameter names:
    878      *            {@link Engine#KEY_PARAM_STREAM},
    879      *            {@link Engine#KEY_PARAM_UTTERANCE_ID},
    880      *            {@link Engine#KEY_PARAM_VOLUME},
    881      *            {@link Engine#KEY_PARAM_PAN}.
    882      *            Engine specific parameters may be passed in but the parameter keys
    883      *            must be prefixed by the name of the engine they are intended for. For example
    884      *            the keys "com.svox.pico_foo" and "com.svox.pico:bar" will be passed to the
    885      *            engine named "com.svox.pico" if it is being used.
    886      *
    887      * @return {@link #ERROR} or {@link #SUCCESS} of <b>queuing</b> the speak operation.
    888      */
    889     public int speak(final String text, final int queueMode, final HashMap<String, String> params) {
    890         return runAction(new Action<Integer>() {
    891             @Override
    892             public Integer run(ITextToSpeechService service) throws RemoteException {
    893                 Uri utteranceUri = mUtterances.get(text);
    894                 if (utteranceUri != null) {
    895                     return service.playAudio(getCallerIdentity(), utteranceUri, queueMode,
    896                             getParams(params));
    897                 } else {
    898                     return service.speak(getCallerIdentity(), text, queueMode, getParams(params));
    899                 }
    900             }
    901         }, ERROR, "speak");
    902     }
    903 
    904     /**
    905      * Plays the earcon using the specified queueing mode and parameters.
    906      * The earcon must already have been added with {@link #addEarcon(String, String)} or
    907      * {@link #addEarcon(String, String, int)}.
    908      * This method is asynchronous, i.e. the method just adds the request to the queue of TTS
    909      * requests and then returns. The synthesis might not have finished (or even started!) at the
    910      * time when this method returns. In order to reliably detect errors during synthesis,
    911      * we recommend setting an utterance progress listener (see
    912      * {@link #setOnUtteranceProgressListener}) and using the
    913      * {@link Engine#KEY_PARAM_UTTERANCE_ID} parameter.
    914      *
    915      * @param earcon The earcon that should be played
    916      * @param queueMode {@link #QUEUE_ADD} or {@link #QUEUE_FLUSH}.
    917      * @param params Parameters for the request. Can be null.
    918      *            Supported parameter names:
    919      *            {@link Engine#KEY_PARAM_STREAM},
    920      *            {@link Engine#KEY_PARAM_UTTERANCE_ID}.
    921      *            Engine specific parameters may be passed in but the parameter keys
    922      *            must be prefixed by the name of the engine they are intended for. For example
    923      *            the keys "com.svox.pico_foo" and "com.svox.pico:bar" will be passed to the
    924      *            engine named "com.svox.pico" if it is being used.
    925      *
    926      * @return {@link #ERROR} or {@link #SUCCESS} of <b>queuing</b> the playEarcon operation.
    927      */
    928     public int playEarcon(final String earcon, final int queueMode,
    929             final HashMap<String, String> params) {
    930         return runAction(new Action<Integer>() {
    931             @Override
    932             public Integer run(ITextToSpeechService service) throws RemoteException {
    933                 Uri earconUri = mEarcons.get(earcon);
    934                 if (earconUri == null) {
    935                     return ERROR;
    936                 }
    937                 return service.playAudio(getCallerIdentity(), earconUri, queueMode,
    938                         getParams(params));
    939             }
    940         }, ERROR, "playEarcon");
    941     }
    942 
    943     /**
    944      * Plays silence for the specified amount of time using the specified
    945      * queue mode.
    946      * This method is asynchronous, i.e. the method just adds the request to the queue of TTS
    947      * requests and then returns. The synthesis might not have finished (or even started!) at the
    948      * time when this method returns. In order to reliably detect errors during synthesis,
    949      * we recommend setting an utterance progress listener (see
    950      * {@link #setOnUtteranceProgressListener}) and using the
    951      * {@link Engine#KEY_PARAM_UTTERANCE_ID} parameter.
    952      *
    953      * @param durationInMs The duration of the silence.
    954      * @param queueMode {@link #QUEUE_ADD} or {@link #QUEUE_FLUSH}.
    955      * @param params Parameters for the request. Can be null.
    956      *            Supported parameter names:
    957      *            {@link Engine#KEY_PARAM_UTTERANCE_ID}.
    958      *            Engine specific parameters may be passed in but the parameter keys
    959      *            must be prefixed by the name of the engine they are intended for. For example
    960      *            the keys "com.svox.pico_foo" and "com.svox.pico:bar" will be passed to the
    961      *            engine named "com.svox.pico" if it is being used.
    962      *
    963      * @return {@link #ERROR} or {@link #SUCCESS} of <b>queuing</b> the playSilence operation.
    964      */
    965     public int playSilence(final long durationInMs, final int queueMode,
    966             final HashMap<String, String> params) {
    967         return runAction(new Action<Integer>() {
    968             @Override
    969             public Integer run(ITextToSpeechService service) throws RemoteException {
    970                 return service.playSilence(getCallerIdentity(), durationInMs, queueMode,
    971                         getParams(params));
    972             }
    973         }, ERROR, "playSilence");
    974     }
    975 
    976     /**
    977      * Queries the engine for the set of features it supports for a given locale.
    978      * Features can either be framework defined, e.g.
    979      * {@link TextToSpeech.Engine#KEY_FEATURE_NETWORK_SYNTHESIS} or engine specific.
    980      * Engine specific keys must be prefixed by the name of the engine they
    981      * are intended for. These keys can be used as parameters to
    982      * {@link TextToSpeech#speak(String, int, java.util.HashMap)} and
    983      * {@link TextToSpeech#synthesizeToFile(String, java.util.HashMap, String)}.
    984      *
    985      * Features are boolean flags, and their values in the synthesis parameters
    986      * must be behave as per {@link Boolean#parseBoolean(String)}.
    987      *
    988      * @param locale The locale to query features for.
    989      */
    990     public Set<String> getFeatures(final Locale locale) {
    991         return runAction(new Action<Set<String>>() {
    992             @Override
    993             public Set<String> run(ITextToSpeechService service) throws RemoteException {
    994                 String[] features = service.getFeaturesForLanguage(
    995                         locale.getISO3Language(), locale.getISO3Country(), locale.getVariant());
    996                 if (features != null) {
    997                     final Set<String> featureSet = new HashSet<String>();
    998                     Collections.addAll(featureSet, features);
    999                     return featureSet;
   1000                 }
   1001                 return null;
   1002             }
   1003         }, null, "getFeatures");
   1004     }
   1005 
   1006     /**
   1007      * Checks whether the TTS engine is busy speaking. Note that a speech item is
   1008      * considered complete once it's audio data has been sent to the audio mixer, or
   1009      * written to a file. There might be a finite lag between this point, and when
   1010      * the audio hardware completes playback.
   1011      *
   1012      * @return {@code true} if the TTS engine is speaking.
   1013      */
   1014     public boolean isSpeaking() {
   1015         return runAction(new Action<Boolean>() {
   1016             @Override
   1017             public Boolean run(ITextToSpeechService service) throws RemoteException {
   1018                 return service.isSpeaking();
   1019             }
   1020         }, false, "isSpeaking");
   1021     }
   1022 
   1023     /**
   1024      * Interrupts the current utterance (whether played or rendered to file) and discards other
   1025      * utterances in the queue.
   1026      *
   1027      * @return {@link #ERROR} or {@link #SUCCESS}.
   1028      */
   1029     public int stop() {
   1030         return runAction(new Action<Integer>() {
   1031             @Override
   1032             public Integer run(ITextToSpeechService service) throws RemoteException {
   1033                 return service.stop(getCallerIdentity());
   1034             }
   1035         }, ERROR, "stop");
   1036     }
   1037 
   1038     /**
   1039      * Sets the speech rate.
   1040      *
   1041      * This has no effect on any pre-recorded speech.
   1042      *
   1043      * @param speechRate Speech rate. {@code 1.0} is the normal speech rate,
   1044      *            lower values slow down the speech ({@code 0.5} is half the normal speech rate),
   1045      *            greater values accelerate it ({@code 2.0} is twice the normal speech rate).
   1046      *
   1047      * @return {@link #ERROR} or {@link #SUCCESS}.
   1048      */
   1049     public int setSpeechRate(float speechRate) {
   1050         if (speechRate > 0.0f) {
   1051             int intRate = (int)(speechRate * 100);
   1052             if (intRate > 0) {
   1053                 synchronized (mStartLock) {
   1054                     mParams.putInt(Engine.KEY_PARAM_RATE, intRate);
   1055                 }
   1056                 return SUCCESS;
   1057             }
   1058         }
   1059         return ERROR;
   1060     }
   1061 
   1062     /**
   1063      * Sets the speech pitch for the TextToSpeech engine.
   1064      *
   1065      * This has no effect on any pre-recorded speech.
   1066      *
   1067      * @param pitch Speech pitch. {@code 1.0} is the normal pitch,
   1068      *            lower values lower the tone of the synthesized voice,
   1069      *            greater values increase it.
   1070      *
   1071      * @return {@link #ERROR} or {@link #SUCCESS}.
   1072      */
   1073     public int setPitch(float pitch) {
   1074         if (pitch > 0.0f) {
   1075             int intPitch = (int)(pitch * 100);
   1076             if (intPitch > 0) {
   1077                 synchronized (mStartLock) {
   1078                     mParams.putInt(Engine.KEY_PARAM_PITCH, intPitch);
   1079                 }
   1080                 return SUCCESS;
   1081             }
   1082         }
   1083         return ERROR;
   1084     }
   1085 
   1086     /**
   1087      * @return the engine currently in use by this TextToSpeech instance.
   1088      * @hide
   1089      */
   1090     public String getCurrentEngine() {
   1091         return mCurrentEngine;
   1092     }
   1093 
   1094     /**
   1095      * Returns a Locale instance describing the language currently being used as the default
   1096      * Text-to-speech language.
   1097      *
   1098      * @return language, country (if any) and variant (if any) used by the client stored in a
   1099      *     Locale instance, or {@code null} on error.
   1100      */
   1101     public Locale getDefaultLanguage() {
   1102         return runAction(new Action<Locale>() {
   1103             @Override
   1104             public Locale run(ITextToSpeechService service) throws RemoteException {
   1105                 String[] defaultLanguage = service.getClientDefaultLanguage();
   1106 
   1107                 return new Locale(defaultLanguage[0], defaultLanguage[1], defaultLanguage[2]);
   1108             }
   1109         }, null, "getDefaultLanguage");
   1110     }
   1111 
   1112     /**
   1113      * Sets the text-to-speech language.
   1114      * The TTS engine will try to use the closest match to the specified
   1115      * language as represented by the Locale, but there is no guarantee that the exact same Locale
   1116      * will be used. Use {@link #isLanguageAvailable(Locale)} to check the level of support
   1117      * before choosing the language to use for the next utterances.
   1118      *
   1119      * @param loc The locale describing the language to be used.
   1120      *
   1121      * @return Code indicating the support status for the locale. See {@link #LANG_AVAILABLE},
   1122      *         {@link #LANG_COUNTRY_AVAILABLE}, {@link #LANG_COUNTRY_VAR_AVAILABLE},
   1123      *         {@link #LANG_MISSING_DATA} and {@link #LANG_NOT_SUPPORTED}.
   1124      */
   1125     public int setLanguage(final Locale loc) {
   1126         return runAction(new Action<Integer>() {
   1127             @Override
   1128             public Integer run(ITextToSpeechService service) throws RemoteException {
   1129                 if (loc == null) {
   1130                     return LANG_NOT_SUPPORTED;
   1131                 }
   1132                 String language = null, country = null;
   1133                 try {
   1134                     language = loc.getISO3Language();
   1135                 } catch (MissingResourceException e) {
   1136                     Log.w(TAG, "Couldn't retrieve ISO 639-2/T language code for locale: " + loc, e);
   1137                     return LANG_NOT_SUPPORTED;
   1138                 }
   1139 
   1140                 try {
   1141                     country = loc.getISO3Country();
   1142                 } catch (MissingResourceException e) {
   1143                     Log.w(TAG, "Couldn't retrieve ISO 3166 country code for locale: " + loc, e);
   1144                     return LANG_NOT_SUPPORTED;
   1145                 }
   1146 
   1147                 String variant = loc.getVariant();
   1148 
   1149                 // Check if the language, country, variant are available, and cache
   1150                 // the available parts.
   1151                 // Note that the language is not actually set here, instead it is cached so it
   1152                 // will be associated with all upcoming utterances.
   1153 
   1154                 int result = service.loadLanguage(getCallerIdentity(), language, country, variant);
   1155                 if (result >= LANG_AVAILABLE){
   1156                     if (result < LANG_COUNTRY_VAR_AVAILABLE) {
   1157                         variant = "";
   1158                         if (result < LANG_COUNTRY_AVAILABLE) {
   1159                             country = "";
   1160                         }
   1161                     }
   1162                     mParams.putString(Engine.KEY_PARAM_LANGUAGE, language);
   1163                     mParams.putString(Engine.KEY_PARAM_COUNTRY, country);
   1164                     mParams.putString(Engine.KEY_PARAM_VARIANT, variant);
   1165                 }
   1166                 return result;
   1167             }
   1168         }, LANG_NOT_SUPPORTED, "setLanguage");
   1169     }
   1170 
   1171     /**
   1172      * Returns a Locale instance describing the language currently being used for synthesis
   1173      * requests sent to the TextToSpeech engine.
   1174      *
   1175      * In Android 4.2 and before (API <= 17) this function returns the language that is currently
   1176      * being used by the TTS engine. That is the last language set by this or any other
   1177      * client by a {@link TextToSpeech#setLanguage} call to the same engine.
   1178      *
   1179      * In Android versions after 4.2 this function returns the language that is currently being
   1180      * used for the synthesis requests sent from this client. That is the last language set
   1181      * by a {@link TextToSpeech#setLanguage} call on this instance.
   1182      *
   1183      * @return language, country (if any) and variant (if any) used by the client stored in a
   1184      *     Locale instance, or {@code null} on error.
   1185      */
   1186     public Locale getLanguage() {
   1187         return runAction(new Action<Locale>() {
   1188             @Override
   1189             public Locale run(ITextToSpeechService service) {
   1190                 /* No service call, but we're accessing mParams, hence need for
   1191                    wrapping it as an Action instance */
   1192                 String lang = mParams.getString(Engine.KEY_PARAM_LANGUAGE, "");
   1193                 String country = mParams.getString(Engine.KEY_PARAM_COUNTRY, "");
   1194                 String variant = mParams.getString(Engine.KEY_PARAM_VARIANT, "");
   1195                 return new Locale(lang, country, variant);
   1196             }
   1197         }, null, "getLanguage");
   1198     }
   1199 
   1200     /**
   1201      * Checks if the specified language as represented by the Locale is available and supported.
   1202      *
   1203      * @param loc The Locale describing the language to be used.
   1204      *
   1205      * @return Code indicating the support status for the locale. See {@link #LANG_AVAILABLE},
   1206      *         {@link #LANG_COUNTRY_AVAILABLE}, {@link #LANG_COUNTRY_VAR_AVAILABLE},
   1207      *         {@link #LANG_MISSING_DATA} and {@link #LANG_NOT_SUPPORTED}.
   1208      */
   1209     public int isLanguageAvailable(final Locale loc) {
   1210         return runAction(new Action<Integer>() {
   1211             @Override
   1212             public Integer run(ITextToSpeechService service) throws RemoteException {
   1213                 String language = null, country = null;
   1214 
   1215                 try {
   1216                     language = loc.getISO3Language();
   1217                 } catch (MissingResourceException e) {
   1218                     Log.w(TAG, "Couldn't retrieve ISO 639-2/T language code for locale: " + loc, e);
   1219                     return LANG_NOT_SUPPORTED;
   1220                 }
   1221 
   1222                 try {
   1223                     country = loc.getISO3Country();
   1224                 } catch (MissingResourceException e) {
   1225                     Log.w(TAG, "Couldn't retrieve ISO 3166 country code for locale: " + loc, e);
   1226                     return LANG_NOT_SUPPORTED;
   1227                 }
   1228 
   1229                 return service.isLanguageAvailable(language, country, loc.getVariant());
   1230             }
   1231         }, LANG_NOT_SUPPORTED, "isLanguageAvailable");
   1232     }
   1233 
   1234     /**
   1235      * Synthesizes the given text to a file using the specified parameters.
   1236      * This method is asynchronous, i.e. the method just adds the request to the queue of TTS
   1237      * requests and then returns. The synthesis might not have finished (or even started!) at the
   1238      * time when this method returns. In order to reliably detect errors during synthesis,
   1239      * we recommend setting an utterance progress listener (see
   1240      * {@link #setOnUtteranceProgressListener}) and using the
   1241      * {@link Engine#KEY_PARAM_UTTERANCE_ID} parameter.
   1242      *
   1243      * @param text The text that should be synthesized. No longer than
   1244      *            {@link #getMaxSpeechInputLength()} characters.
   1245      * @param params Parameters for the request. Can be null.
   1246      *            Supported parameter names:
   1247      *            {@link Engine#KEY_PARAM_UTTERANCE_ID}.
   1248      *            Engine specific parameters may be passed in but the parameter keys
   1249      *            must be prefixed by the name of the engine they are intended for. For example
   1250      *            the keys "com.svox.pico_foo" and "com.svox.pico:bar" will be passed to the
   1251      *            engine named "com.svox.pico" if it is being used.
   1252      * @param filename Absolute file filename to write the generated audio data to.It should be
   1253      *            something like "/sdcard/myappsounds/mysound.wav".
   1254      *
   1255      * @return {@link #ERROR} or {@link #SUCCESS} of <b>queuing</b> the synthesizeToFile operation.
   1256      */
   1257     public int synthesizeToFile(final String text, final HashMap<String, String> params,
   1258             final String filename) {
   1259         return runAction(new Action<Integer>() {
   1260             @Override
   1261             public Integer run(ITextToSpeechService service) throws RemoteException {
   1262                 ParcelFileDescriptor fileDescriptor;
   1263                 int returnValue;
   1264                 try {
   1265                     File file = new File(filename);
   1266                     if(file.exists() && !file.canWrite()) {
   1267                         Log.e(TAG, "Can't write to " + filename);
   1268                         return ERROR;
   1269                     }
   1270                     fileDescriptor = ParcelFileDescriptor.open(file,
   1271                             ParcelFileDescriptor.MODE_WRITE_ONLY |
   1272                             ParcelFileDescriptor.MODE_CREATE |
   1273                             ParcelFileDescriptor.MODE_TRUNCATE);
   1274                     returnValue = service.synthesizeToFileDescriptor(getCallerIdentity(), text,
   1275                             fileDescriptor, getParams(params));
   1276                     fileDescriptor.close();
   1277                     return returnValue;
   1278                 } catch (FileNotFoundException e) {
   1279                     Log.e(TAG, "Opening file " + filename + " failed", e);
   1280                     return ERROR;
   1281                 } catch (IOException e) {
   1282                     Log.e(TAG, "Closing file " + filename + " failed", e);
   1283                     return ERROR;
   1284                 }
   1285             }
   1286         }, ERROR, "synthesizeToFile");
   1287     }
   1288 
   1289     private Bundle getParams(HashMap<String, String> params) {
   1290         if (params != null && !params.isEmpty()) {
   1291             Bundle bundle = new Bundle(mParams);
   1292             copyIntParam(bundle, params, Engine.KEY_PARAM_STREAM);
   1293             copyStringParam(bundle, params, Engine.KEY_PARAM_UTTERANCE_ID);
   1294             copyFloatParam(bundle, params, Engine.KEY_PARAM_VOLUME);
   1295             copyFloatParam(bundle, params, Engine.KEY_PARAM_PAN);
   1296 
   1297             // Copy feature strings defined by the framework.
   1298             copyStringParam(bundle, params, Engine.KEY_FEATURE_NETWORK_SYNTHESIS);
   1299             copyStringParam(bundle, params, Engine.KEY_FEATURE_EMBEDDED_SYNTHESIS);
   1300 
   1301             // Copy over all parameters that start with the name of the
   1302             // engine that we are currently connected to. The engine is
   1303             // free to interpret them as it chooses.
   1304             if (!TextUtils.isEmpty(mCurrentEngine)) {
   1305                 for (Map.Entry<String, String> entry : params.entrySet()) {
   1306                     final String key = entry.getKey();
   1307                     if (key != null && key.startsWith(mCurrentEngine)) {
   1308                         bundle.putString(key, entry.getValue());
   1309                     }
   1310                 }
   1311             }
   1312 
   1313             return bundle;
   1314         } else {
   1315             return mParams;
   1316         }
   1317     }
   1318 
   1319     private void copyStringParam(Bundle bundle, HashMap<String, String> params, String key) {
   1320         String value = params.get(key);
   1321         if (value != null) {
   1322             bundle.putString(key, value);
   1323         }
   1324     }
   1325 
   1326     private void copyIntParam(Bundle bundle, HashMap<String, String> params, String key) {
   1327         String valueString = params.get(key);
   1328         if (!TextUtils.isEmpty(valueString)) {
   1329             try {
   1330                 int value = Integer.parseInt(valueString);
   1331                 bundle.putInt(key, value);
   1332             } catch (NumberFormatException ex) {
   1333                 // don't set the value in the bundle
   1334             }
   1335         }
   1336     }
   1337 
   1338     private void copyFloatParam(Bundle bundle, HashMap<String, String> params, String key) {
   1339         String valueString = params.get(key);
   1340         if (!TextUtils.isEmpty(valueString)) {
   1341             try {
   1342                 float value = Float.parseFloat(valueString);
   1343                 bundle.putFloat(key, value);
   1344             } catch (NumberFormatException ex) {
   1345                 // don't set the value in the bundle
   1346             }
   1347         }
   1348     }
   1349 
   1350     /**
   1351      * Sets the listener that will be notified when synthesis of an utterance completes.
   1352      *
   1353      * @param listener The listener to use.
   1354      *
   1355      * @return {@link #ERROR} or {@link #SUCCESS}.
   1356      *
   1357      * @deprecated Use {@link #setOnUtteranceProgressListener(UtteranceProgressListener)}
   1358      *        instead.
   1359      */
   1360     @Deprecated
   1361     public int setOnUtteranceCompletedListener(final OnUtteranceCompletedListener listener) {
   1362         mUtteranceProgressListener = UtteranceProgressListener.from(listener);
   1363         return TextToSpeech.SUCCESS;
   1364     }
   1365 
   1366     /**
   1367      * Sets the listener that will be notified of various events related to the
   1368      * synthesis of a given utterance.
   1369      *
   1370      * See {@link UtteranceProgressListener} and
   1371      * {@link TextToSpeech.Engine#KEY_PARAM_UTTERANCE_ID}.
   1372      *
   1373      * @param listener the listener to use.
   1374      * @return {@link #ERROR} or {@link #SUCCESS}
   1375      */
   1376     public int setOnUtteranceProgressListener(UtteranceProgressListener listener) {
   1377         mUtteranceProgressListener = listener;
   1378         return TextToSpeech.SUCCESS;
   1379     }
   1380 
   1381     /**
   1382      * Sets the TTS engine to use.
   1383      *
   1384      * @deprecated This doesn't inform callers when the TTS engine has been
   1385      *        initialized. {@link #TextToSpeech(Context, OnInitListener, String)}
   1386      *        can be used with the appropriate engine name. Also, there is no
   1387      *        guarantee that the engine specified will be loaded. If it isn't
   1388      *        installed or disabled, the user / system wide defaults will apply.
   1389      *
   1390      * @param enginePackageName The package name for the synthesis engine (e.g. "com.svox.pico")
   1391      *
   1392      * @return {@link #ERROR} or {@link #SUCCESS}.
   1393      */
   1394     @Deprecated
   1395     public int setEngineByPackageName(String enginePackageName) {
   1396         mRequestedEngine = enginePackageName;
   1397         return initTts();
   1398     }
   1399 
   1400     /**
   1401      * Gets the package name of the default speech synthesis engine.
   1402      *
   1403      * @return Package name of the TTS engine that the user has chosen
   1404      *        as their default.
   1405      */
   1406     public String getDefaultEngine() {
   1407         return mEnginesHelper.getDefaultEngine();
   1408     }
   1409 
   1410     /**
   1411      * Checks whether the user's settings should override settings requested
   1412      * by the calling application. As of the Ice cream sandwich release,
   1413      * user settings never forcibly override the app's settings.
   1414      */
   1415     public boolean areDefaultsEnforced() {
   1416         return false;
   1417     }
   1418 
   1419     /**
   1420      * Gets a list of all installed TTS engines.
   1421      *
   1422      * @return A list of engine info objects. The list can be empty, but never {@code null}.
   1423      */
   1424     public List<EngineInfo> getEngines() {
   1425         return mEnginesHelper.getEngines();
   1426     }
   1427 
   1428     private class Connection implements ServiceConnection {
   1429         private ITextToSpeechService mService;
   1430 
   1431         private SetupConnectionAsyncTask mOnSetupConnectionAsyncTask;
   1432 
   1433         private boolean mEstablished;
   1434 
   1435         private final ITextToSpeechCallback.Stub mCallback = new ITextToSpeechCallback.Stub() {
   1436             @Override
   1437             public void onDone(String utteranceId) {
   1438                 UtteranceProgressListener listener = mUtteranceProgressListener;
   1439                 if (listener != null) {
   1440                     listener.onDone(utteranceId);
   1441                 }
   1442             }
   1443 
   1444             @Override
   1445             public void onError(String utteranceId) {
   1446                 UtteranceProgressListener listener = mUtteranceProgressListener;
   1447                 if (listener != null) {
   1448                     listener.onError(utteranceId);
   1449                 }
   1450             }
   1451 
   1452             @Override
   1453             public void onStart(String utteranceId) {
   1454                 UtteranceProgressListener listener = mUtteranceProgressListener;
   1455                 if (listener != null) {
   1456                     listener.onStart(utteranceId);
   1457                 }
   1458             }
   1459         };
   1460 
   1461         private class SetupConnectionAsyncTask extends AsyncTask<Void, Void, Integer> {
   1462             private final ComponentName mName;
   1463 
   1464             public SetupConnectionAsyncTask(ComponentName name) {
   1465                 mName = name;
   1466             }
   1467 
   1468             @Override
   1469             protected Integer doInBackground(Void... params) {
   1470                 synchronized(mStartLock) {
   1471                     if (isCancelled()) {
   1472                         return null;
   1473                     }
   1474 
   1475                     try {
   1476                         mService.setCallback(getCallerIdentity(), mCallback);
   1477                         String[] defaultLanguage = mService.getClientDefaultLanguage();
   1478 
   1479                         mParams.putString(Engine.KEY_PARAM_LANGUAGE, defaultLanguage[0]);
   1480                         mParams.putString(Engine.KEY_PARAM_COUNTRY, defaultLanguage[1]);
   1481                         mParams.putString(Engine.KEY_PARAM_VARIANT, defaultLanguage[2]);
   1482 
   1483                         Log.i(TAG, "Set up connection to " + mName);
   1484                         return SUCCESS;
   1485                     } catch (RemoteException re) {
   1486                         Log.e(TAG, "Error connecting to service, setCallback() failed");
   1487                         return ERROR;
   1488                     }
   1489                 }
   1490             }
   1491 
   1492             @Override
   1493             protected void onPostExecute(Integer result) {
   1494                 synchronized(mStartLock) {
   1495                     if (mOnSetupConnectionAsyncTask == this) {
   1496                         mOnSetupConnectionAsyncTask = null;
   1497                     }
   1498                     mEstablished = true;
   1499                     dispatchOnInit(result);
   1500                 }
   1501             }
   1502         }
   1503 
   1504         @Override
   1505         public void onServiceConnected(ComponentName name, IBinder service) {
   1506             synchronized(mStartLock) {
   1507                 mConnectingServiceConnection = null;
   1508 
   1509                 Log.i(TAG, "Connected to " + name);
   1510 
   1511                 if (mOnSetupConnectionAsyncTask != null) {
   1512                     mOnSetupConnectionAsyncTask.cancel(false);
   1513                 }
   1514 
   1515                 mService = ITextToSpeechService.Stub.asInterface(service);
   1516                 mServiceConnection = Connection.this;
   1517 
   1518                 mEstablished = false;
   1519                 mOnSetupConnectionAsyncTask = new SetupConnectionAsyncTask(name);
   1520                 mOnSetupConnectionAsyncTask.execute();
   1521             }
   1522         }
   1523 
   1524         public IBinder getCallerIdentity() {
   1525             return mCallback;
   1526         }
   1527 
   1528         /**
   1529          * Clear connection related fields and cancel mOnServiceConnectedAsyncTask if set.
   1530          *
   1531          * @return true if we cancel mOnSetupConnectionAsyncTask in progress.
   1532          */
   1533         private boolean clearServiceConnection() {
   1534             synchronized(mStartLock) {
   1535                 boolean result = false;
   1536                 if (mOnSetupConnectionAsyncTask != null) {
   1537                     result = mOnSetupConnectionAsyncTask.cancel(false);
   1538                     mOnSetupConnectionAsyncTask = null;
   1539                 }
   1540 
   1541                 mService = null;
   1542                 // If this is the active connection, clear it
   1543                 if (mServiceConnection == this) {
   1544                     mServiceConnection = null;
   1545                 }
   1546                 return result;
   1547             }
   1548         }
   1549 
   1550         @Override
   1551         public void onServiceDisconnected(ComponentName name) {
   1552             Log.i(TAG, "Asked to disconnect from " + name);
   1553             if (clearServiceConnection()) {
   1554                 /* We need to protect against a rare case where engine
   1555                  * dies just after successful connection - and we process onServiceDisconnected
   1556                  * before OnServiceConnectedAsyncTask.onPostExecute. onServiceDisconnected cancels
   1557                  * OnServiceConnectedAsyncTask.onPostExecute and we don't call dispatchOnInit
   1558                  * with ERROR as argument.
   1559                  */
   1560                 dispatchOnInit(ERROR);
   1561             }
   1562         }
   1563 
   1564         public void disconnect() {
   1565             mContext.unbindService(this);
   1566             clearServiceConnection();
   1567         }
   1568 
   1569         public boolean isEstablished() {
   1570             return mService != null && mEstablished;
   1571         }
   1572 
   1573         public <R> R runAction(Action<R> action, R errorResult, String method,
   1574                 boolean reconnect, boolean onlyEstablishedConnection) {
   1575             synchronized (mStartLock) {
   1576                 try {
   1577                     if (mService == null) {
   1578                         Log.w(TAG, method + " failed: not connected to TTS engine");
   1579                         return errorResult;
   1580                     }
   1581                     if (onlyEstablishedConnection && !isEstablished()) {
   1582                         Log.w(TAG, method + " failed: TTS engine connection not fully set up");
   1583                         return errorResult;
   1584                     }
   1585                     return action.run(mService);
   1586                 } catch (RemoteException ex) {
   1587                     Log.e(TAG, method + " failed", ex);
   1588                     if (reconnect) {
   1589                         disconnect();
   1590                         initTts();
   1591                     }
   1592                     return errorResult;
   1593                 }
   1594             }
   1595         }
   1596     }
   1597 
   1598     private interface Action<R> {
   1599         R run(ITextToSpeechService service) throws RemoteException;
   1600     }
   1601 
   1602     /**
   1603      * Information about an installed text-to-speech engine.
   1604      *
   1605      * @see TextToSpeech#getEngines
   1606      */
   1607     public static class EngineInfo {
   1608         /**
   1609          * Engine package name..
   1610          */
   1611         public String name;
   1612         /**
   1613          * Localized label for the engine.
   1614          */
   1615         public String label;
   1616         /**
   1617          * Icon for the engine.
   1618          */
   1619         public int icon;
   1620         /**
   1621          * Whether this engine is a part of the system
   1622          * image.
   1623          *
   1624          * @hide
   1625          */
   1626         public boolean system;
   1627         /**
   1628          * The priority the engine declares for the the intent filter
   1629          * {@code android.intent.action.TTS_SERVICE}
   1630          *
   1631          * @hide
   1632          */
   1633         public int priority;
   1634 
   1635         @Override
   1636         public String toString() {
   1637             return "EngineInfo{name=" + name + "}";
   1638         }
   1639 
   1640     }
   1641 
   1642     /**
   1643      * Limit of length of input string passed to speak and synthesizeToFile.
   1644      *
   1645      * @see #speak
   1646      * @see #synthesizeToFile
   1647      */
   1648     public static int getMaxSpeechInputLength() {
   1649         return 4000;
   1650     }
   1651 }
   1652