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. In a case of a failure the listener
    565      *            may be called immediately, before TextToSpeech instance is fully constructed.
    566      */
    567     public TextToSpeech(Context context, OnInitListener listener) {
    568         this(context, listener, null);
    569     }
    570 
    571     /**
    572      * The constructor for the TextToSpeech class, using the given TTS engine.
    573      * This will also initialize the associated TextToSpeech engine if it isn't already running.
    574      *
    575      * @param context
    576      *            The context this instance is running in.
    577      * @param listener
    578      *            The {@link TextToSpeech.OnInitListener} that will be called when the
    579      *            TextToSpeech engine has initialized. In a case of a failure the listener
    580      *            may be called immediately, before TextToSpeech instance is fully constructed.
    581      * @param engine Package name of the TTS engine to use.
    582      */
    583     public TextToSpeech(Context context, OnInitListener listener, String engine) {
    584         this(context, listener, engine, null, true);
    585     }
    586 
    587     /**
    588      * Used by the framework to instantiate TextToSpeech objects with a supplied
    589      * package name, instead of using {@link android.content.Context#getPackageName()}
    590      *
    591      * @hide
    592      */
    593     public TextToSpeech(Context context, OnInitListener listener, String engine,
    594             String packageName, boolean useFallback) {
    595         mContext = context;
    596         mInitListener = listener;
    597         mRequestedEngine = engine;
    598         mUseFallback = useFallback;
    599 
    600         mEarcons = new HashMap<String, Uri>();
    601         mUtterances = new HashMap<String, Uri>();
    602         mUtteranceProgressListener = null;
    603 
    604         mEnginesHelper = new TtsEngines(mContext);
    605         if (packageName != null) {
    606             mPackageName = packageName;
    607         } else {
    608             mPackageName = mContext.getPackageName();
    609         }
    610         initTts();
    611     }
    612 
    613     private <R> R runActionNoReconnect(Action<R> action, R errorResult, String method,
    614             boolean onlyEstablishedConnection) {
    615         return runAction(action, errorResult, method, false, onlyEstablishedConnection);
    616     }
    617 
    618     private <R> R runAction(Action<R> action, R errorResult, String method) {
    619         return runAction(action, errorResult, method, true, true);
    620     }
    621 
    622     private <R> R runAction(Action<R> action, R errorResult, String method,
    623             boolean reconnect, boolean onlyEstablishedConnection) {
    624         synchronized (mStartLock) {
    625             if (mServiceConnection == null) {
    626                 Log.w(TAG, method + " failed: not bound to TTS engine");
    627                 return errorResult;
    628             }
    629             return mServiceConnection.runAction(action, errorResult, method, reconnect,
    630                     onlyEstablishedConnection);
    631         }
    632     }
    633 
    634     private int initTts() {
    635         // Step 1: Try connecting to the engine that was requested.
    636         if (mRequestedEngine != null) {
    637             if (mEnginesHelper.isEngineInstalled(mRequestedEngine)) {
    638                 if (connectToEngine(mRequestedEngine)) {
    639                     mCurrentEngine = mRequestedEngine;
    640                     return SUCCESS;
    641                 } else if (!mUseFallback) {
    642                     mCurrentEngine = null;
    643                     dispatchOnInit(ERROR);
    644                     return ERROR;
    645                 }
    646             } else if (!mUseFallback) {
    647                 Log.i(TAG, "Requested engine not installed: " + mRequestedEngine);
    648                 mCurrentEngine = null;
    649                 dispatchOnInit(ERROR);
    650                 return ERROR;
    651             }
    652         }
    653 
    654         // Step 2: Try connecting to the user's default engine.
    655         final String defaultEngine = getDefaultEngine();
    656         if (defaultEngine != null && !defaultEngine.equals(mRequestedEngine)) {
    657             if (connectToEngine(defaultEngine)) {
    658                 mCurrentEngine = defaultEngine;
    659                 return SUCCESS;
    660             }
    661         }
    662 
    663         // Step 3: Try connecting to the highest ranked engine in the
    664         // system.
    665         final String highestRanked = mEnginesHelper.getHighestRankedEngineName();
    666         if (highestRanked != null && !highestRanked.equals(mRequestedEngine) &&
    667                 !highestRanked.equals(defaultEngine)) {
    668             if (connectToEngine(highestRanked)) {
    669                 mCurrentEngine = highestRanked;
    670                 return SUCCESS;
    671             }
    672         }
    673 
    674         // NOTE: The API currently does not allow the caller to query whether
    675         // they are actually connected to any engine. This might fail for various
    676         // reasons like if the user disables all her TTS engines.
    677 
    678         mCurrentEngine = null;
    679         dispatchOnInit(ERROR);
    680         return ERROR;
    681     }
    682 
    683     private boolean connectToEngine(String engine) {
    684         Connection connection = new Connection();
    685         Intent intent = new Intent(Engine.INTENT_ACTION_TTS_SERVICE);
    686         intent.setPackage(engine);
    687         boolean bound = mContext.bindService(intent, connection, Context.BIND_AUTO_CREATE);
    688         if (!bound) {
    689             Log.e(TAG, "Failed to bind to " + engine);
    690             return false;
    691         } else {
    692             Log.i(TAG, "Sucessfully bound to " + engine);
    693             mConnectingServiceConnection = connection;
    694             return true;
    695         }
    696     }
    697 
    698     private void dispatchOnInit(int result) {
    699         synchronized (mStartLock) {
    700             if (mInitListener != null) {
    701                 mInitListener.onInit(result);
    702                 mInitListener = null;
    703             }
    704         }
    705     }
    706 
    707     private IBinder getCallerIdentity() {
    708         return mServiceConnection.getCallerIdentity();
    709     }
    710 
    711     /**
    712      * Releases the resources used by the TextToSpeech engine.
    713      * It is good practice for instance to call this method in the onDestroy() method of an Activity
    714      * so the TextToSpeech engine can be cleanly stopped.
    715      */
    716     public void shutdown() {
    717         // Special case, we are asked to shutdown connection that did finalize its connection.
    718         synchronized (mStartLock) {
    719             if (mConnectingServiceConnection != null) {
    720                 mContext.unbindService(mConnectingServiceConnection);
    721                 mConnectingServiceConnection = null;
    722                 return;
    723             }
    724         }
    725 
    726         // Post connection case
    727         runActionNoReconnect(new Action<Void>() {
    728             @Override
    729             public Void run(ITextToSpeechService service) throws RemoteException {
    730                 service.setCallback(getCallerIdentity(), null);
    731                 service.stop(getCallerIdentity());
    732                 mServiceConnection.disconnect();
    733                 // Context#unbindService does not result in a call to
    734                 // ServiceConnection#onServiceDisconnected. As a result, the
    735                 // service ends up being destroyed (if there are no other open
    736                 // connections to it) but the process lives on and the
    737                 // ServiceConnection continues to refer to the destroyed service.
    738                 //
    739                 // This leads to tons of log spam about SynthThread being dead.
    740                 mServiceConnection = null;
    741                 mCurrentEngine = null;
    742                 return null;
    743             }
    744         }, null, "shutdown", false);
    745     }
    746 
    747     /**
    748      * Adds a mapping between a string of text and a sound resource in a
    749      * package. After a call to this method, subsequent calls to
    750      * {@link #speak(String, int, HashMap)} will play the specified sound resource
    751      * if it is available, or synthesize the text it is missing.
    752      *
    753      * @param text
    754      *            The string of text. Example: <code>"south_south_east"</code>
    755      *
    756      * @param packagename
    757      *            Pass the packagename of the application that contains the
    758      *            resource. If the resource is in your own application (this is
    759      *            the most common case), then put the packagename of your
    760      *            application here.<br/>
    761      *            Example: <b>"com.google.marvin.compass"</b><br/>
    762      *            The packagename can be found in the AndroidManifest.xml of
    763      *            your application.
    764      *            <p>
    765      *            <code>&lt;manifest xmlns:android=&quot;...&quot;
    766      *      package=&quot;<b>com.google.marvin.compass</b>&quot;&gt;</code>
    767      *            </p>
    768      *
    769      * @param resourceId
    770      *            Example: <code>R.raw.south_south_east</code>
    771      *
    772      * @return Code indicating success or failure. See {@link #ERROR} and {@link #SUCCESS}.
    773      */
    774     public int addSpeech(String text, String packagename, int resourceId) {
    775         synchronized (mStartLock) {
    776             mUtterances.put(text, makeResourceUri(packagename, resourceId));
    777             return SUCCESS;
    778         }
    779     }
    780 
    781     /**
    782      * Adds a mapping between a string of text and a sound file. Using this, it
    783      * is possible to add custom pronounciations for a string of text.
    784      * After a call to this method, subsequent calls to {@link #speak(String, int, HashMap)}
    785      * will play the specified sound resource if it is available, or synthesize the text it is
    786      * missing.
    787      *
    788      * @param text
    789      *            The string of text. Example: <code>"south_south_east"</code>
    790      * @param filename
    791      *            The full path to the sound file (for example:
    792      *            "/sdcard/mysounds/hello.wav")
    793      *
    794      * @return Code indicating success or failure. See {@link #ERROR} and {@link #SUCCESS}.
    795      */
    796     public int addSpeech(String text, String filename) {
    797         synchronized (mStartLock) {
    798             mUtterances.put(text, Uri.parse(filename));
    799             return SUCCESS;
    800         }
    801     }
    802 
    803 
    804     /**
    805      * Adds a mapping between a string of text and a sound resource in a
    806      * package. Use this to add custom earcons.
    807      *
    808      * @see #playEarcon(String, int, HashMap)
    809      *
    810      * @param earcon The name of the earcon.
    811      *            Example: <code>"[tick]"</code><br/>
    812      *
    813      * @param packagename
    814      *            the package name of the application that contains the
    815      *            resource. This can for instance be the package name of your own application.
    816      *            Example: <b>"com.google.marvin.compass"</b><br/>
    817      *            The package name can be found in the AndroidManifest.xml of
    818      *            the application containing the resource.
    819      *            <p>
    820      *            <code>&lt;manifest xmlns:android=&quot;...&quot;
    821      *      package=&quot;<b>com.google.marvin.compass</b>&quot;&gt;</code>
    822      *            </p>
    823      *
    824      * @param resourceId
    825      *            Example: <code>R.raw.tick_snd</code>
    826      *
    827      * @return Code indicating success or failure. See {@link #ERROR} and {@link #SUCCESS}.
    828      */
    829     public int addEarcon(String earcon, String packagename, int resourceId) {
    830         synchronized(mStartLock) {
    831             mEarcons.put(earcon, makeResourceUri(packagename, resourceId));
    832             return SUCCESS;
    833         }
    834     }
    835 
    836     /**
    837      * Adds a mapping between a string of text and a sound file.
    838      * Use this to add custom earcons.
    839      *
    840      * @see #playEarcon(String, int, HashMap)
    841      *
    842      * @param earcon
    843      *            The name of the earcon.
    844      *            Example: <code>"[tick]"</code>
    845      * @param filename
    846      *            The full path to the sound file (for example:
    847      *            "/sdcard/mysounds/tick.wav")
    848      *
    849      * @return Code indicating success or failure. See {@link #ERROR} and {@link #SUCCESS}.
    850      */
    851     public int addEarcon(String earcon, String filename) {
    852         synchronized(mStartLock) {
    853             mEarcons.put(earcon, Uri.parse(filename));
    854             return SUCCESS;
    855         }
    856     }
    857 
    858     private Uri makeResourceUri(String packageName, int resourceId) {
    859         return new Uri.Builder()
    860                 .scheme(ContentResolver.SCHEME_ANDROID_RESOURCE)
    861                 .encodedAuthority(packageName)
    862                 .appendEncodedPath(String.valueOf(resourceId))
    863                 .build();
    864     }
    865 
    866     /**
    867      * Speaks the string using the specified queuing strategy and speech parameters.
    868      * This method is asynchronous, i.e. the method just adds the request to the queue of TTS
    869      * requests and then returns. The synthesis might not have finished (or even started!) at the
    870      * time when this method returns. In order to reliably detect errors during synthesis,
    871      * we recommend setting an utterance progress listener (see
    872      * {@link #setOnUtteranceProgressListener}) and using the
    873      * {@link Engine#KEY_PARAM_UTTERANCE_ID} parameter.
    874      *
    875      * @param text The string of text to be spoken. No longer than
    876      *            {@link #getMaxSpeechInputLength()} characters.
    877      * @param queueMode The queuing strategy to use, {@link #QUEUE_ADD} or {@link #QUEUE_FLUSH}.
    878      * @param params Parameters for the request. Can be null.
    879      *            Supported parameter names:
    880      *            {@link Engine#KEY_PARAM_STREAM},
    881      *            {@link Engine#KEY_PARAM_UTTERANCE_ID},
    882      *            {@link Engine#KEY_PARAM_VOLUME},
    883      *            {@link Engine#KEY_PARAM_PAN}.
    884      *            Engine specific parameters may be passed in but the parameter keys
    885      *            must be prefixed by the name of the engine they are intended for. For example
    886      *            the keys "com.svox.pico_foo" and "com.svox.pico:bar" will be passed to the
    887      *            engine named "com.svox.pico" if it is being used.
    888      *
    889      * @return {@link #ERROR} or {@link #SUCCESS} of <b>queuing</b> the speak operation.
    890      */
    891     public int speak(final String text, final int queueMode, final HashMap<String, String> params) {
    892         return runAction(new Action<Integer>() {
    893             @Override
    894             public Integer run(ITextToSpeechService service) throws RemoteException {
    895                 Uri utteranceUri = mUtterances.get(text);
    896                 if (utteranceUri != null) {
    897                     return service.playAudio(getCallerIdentity(), utteranceUri, queueMode,
    898                             getParams(params));
    899                 } else {
    900                     return service.speak(getCallerIdentity(), text, queueMode, getParams(params));
    901                 }
    902             }
    903         }, ERROR, "speak");
    904     }
    905 
    906     /**
    907      * Plays the earcon using the specified queueing mode and parameters.
    908      * The earcon must already have been added with {@link #addEarcon(String, String)} or
    909      * {@link #addEarcon(String, String, int)}.
    910      * This method is asynchronous, i.e. the method just adds the request to the queue of TTS
    911      * requests and then returns. The synthesis might not have finished (or even started!) at the
    912      * time when this method returns. In order to reliably detect errors during synthesis,
    913      * we recommend setting an utterance progress listener (see
    914      * {@link #setOnUtteranceProgressListener}) and using the
    915      * {@link Engine#KEY_PARAM_UTTERANCE_ID} parameter.
    916      *
    917      * @param earcon The earcon that should be played
    918      * @param queueMode {@link #QUEUE_ADD} or {@link #QUEUE_FLUSH}.
    919      * @param params Parameters for the request. Can be null.
    920      *            Supported parameter names:
    921      *            {@link Engine#KEY_PARAM_STREAM},
    922      *            {@link Engine#KEY_PARAM_UTTERANCE_ID}.
    923      *            Engine specific parameters may be passed in but the parameter keys
    924      *            must be prefixed by the name of the engine they are intended for. For example
    925      *            the keys "com.svox.pico_foo" and "com.svox.pico:bar" will be passed to the
    926      *            engine named "com.svox.pico" if it is being used.
    927      *
    928      * @return {@link #ERROR} or {@link #SUCCESS} of <b>queuing</b> the playEarcon operation.
    929      */
    930     public int playEarcon(final String earcon, final int queueMode,
    931             final HashMap<String, String> params) {
    932         return runAction(new Action<Integer>() {
    933             @Override
    934             public Integer run(ITextToSpeechService service) throws RemoteException {
    935                 Uri earconUri = mEarcons.get(earcon);
    936                 if (earconUri == null) {
    937                     return ERROR;
    938                 }
    939                 return service.playAudio(getCallerIdentity(), earconUri, queueMode,
    940                         getParams(params));
    941             }
    942         }, ERROR, "playEarcon");
    943     }
    944 
    945     /**
    946      * Plays silence for the specified amount of time using the specified
    947      * queue mode.
    948      * This method is asynchronous, i.e. the method just adds the request to the queue of TTS
    949      * requests and then returns. The synthesis might not have finished (or even started!) at the
    950      * time when this method returns. In order to reliably detect errors during synthesis,
    951      * we recommend setting an utterance progress listener (see
    952      * {@link #setOnUtteranceProgressListener}) and using the
    953      * {@link Engine#KEY_PARAM_UTTERANCE_ID} parameter.
    954      *
    955      * @param durationInMs The duration of the silence.
    956      * @param queueMode {@link #QUEUE_ADD} or {@link #QUEUE_FLUSH}.
    957      * @param params Parameters for the request. Can be null.
    958      *            Supported parameter names:
    959      *            {@link Engine#KEY_PARAM_UTTERANCE_ID}.
    960      *            Engine specific parameters may be passed in but the parameter keys
    961      *            must be prefixed by the name of the engine they are intended for. For example
    962      *            the keys "com.svox.pico_foo" and "com.svox.pico:bar" will be passed to the
    963      *            engine named "com.svox.pico" if it is being used.
    964      *
    965      * @return {@link #ERROR} or {@link #SUCCESS} of <b>queuing</b> the playSilence operation.
    966      */
    967     public int playSilence(final long durationInMs, final int queueMode,
    968             final HashMap<String, String> params) {
    969         return runAction(new Action<Integer>() {
    970             @Override
    971             public Integer run(ITextToSpeechService service) throws RemoteException {
    972                 return service.playSilence(getCallerIdentity(), durationInMs, queueMode,
    973                         getParams(params));
    974             }
    975         }, ERROR, "playSilence");
    976     }
    977 
    978     /**
    979      * Queries the engine for the set of features it supports for a given locale.
    980      * Features can either be framework defined, e.g.
    981      * {@link TextToSpeech.Engine#KEY_FEATURE_NETWORK_SYNTHESIS} or engine specific.
    982      * Engine specific keys must be prefixed by the name of the engine they
    983      * are intended for. These keys can be used as parameters to
    984      * {@link TextToSpeech#speak(String, int, java.util.HashMap)} and
    985      * {@link TextToSpeech#synthesizeToFile(String, java.util.HashMap, String)}.
    986      *
    987      * Features are boolean flags, and their values in the synthesis parameters
    988      * must be behave as per {@link Boolean#parseBoolean(String)}.
    989      *
    990      * @param locale The locale to query features for.
    991      */
    992     public Set<String> getFeatures(final Locale locale) {
    993         return runAction(new Action<Set<String>>() {
    994             @Override
    995             public Set<String> run(ITextToSpeechService service) throws RemoteException {
    996                 String[] features = null;
    997                 try {
    998                     features = service.getFeaturesForLanguage(
    999                         locale.getISO3Language(), locale.getISO3Country(), locale.getVariant());
   1000                 } catch(MissingResourceException e) {
   1001                     Log.w(TAG, "Couldn't retrieve 3 letter ISO 639-2/T language and/or ISO 3166 " +
   1002                             "country code for locale: " + locale, e);
   1003                     return null;
   1004                 }
   1005 
   1006                 if (features != null) {
   1007                     final Set<String> featureSet = new HashSet<String>();
   1008                     Collections.addAll(featureSet, features);
   1009                     return featureSet;
   1010                 }
   1011                 return null;
   1012             }
   1013         }, null, "getFeatures");
   1014     }
   1015 
   1016     /**
   1017      * Checks whether the TTS engine is busy speaking. Note that a speech item is
   1018      * considered complete once it's audio data has been sent to the audio mixer, or
   1019      * written to a file. There might be a finite lag between this point, and when
   1020      * the audio hardware completes playback.
   1021      *
   1022      * @return {@code true} if the TTS engine is speaking.
   1023      */
   1024     public boolean isSpeaking() {
   1025         return runAction(new Action<Boolean>() {
   1026             @Override
   1027             public Boolean run(ITextToSpeechService service) throws RemoteException {
   1028                 return service.isSpeaking();
   1029             }
   1030         }, false, "isSpeaking");
   1031     }
   1032 
   1033     /**
   1034      * Interrupts the current utterance (whether played or rendered to file) and discards other
   1035      * utterances in the queue.
   1036      *
   1037      * @return {@link #ERROR} or {@link #SUCCESS}.
   1038      */
   1039     public int stop() {
   1040         return runAction(new Action<Integer>() {
   1041             @Override
   1042             public Integer run(ITextToSpeechService service) throws RemoteException {
   1043                 return service.stop(getCallerIdentity());
   1044             }
   1045         }, ERROR, "stop");
   1046     }
   1047 
   1048     /**
   1049      * Sets the speech rate.
   1050      *
   1051      * This has no effect on any pre-recorded speech.
   1052      *
   1053      * @param speechRate Speech rate. {@code 1.0} is the normal speech rate,
   1054      *            lower values slow down the speech ({@code 0.5} is half the normal speech rate),
   1055      *            greater values accelerate it ({@code 2.0} is twice the normal speech rate).
   1056      *
   1057      * @return {@link #ERROR} or {@link #SUCCESS}.
   1058      */
   1059     public int setSpeechRate(float speechRate) {
   1060         if (speechRate > 0.0f) {
   1061             int intRate = (int)(speechRate * 100);
   1062             if (intRate > 0) {
   1063                 synchronized (mStartLock) {
   1064                     mParams.putInt(Engine.KEY_PARAM_RATE, intRate);
   1065                 }
   1066                 return SUCCESS;
   1067             }
   1068         }
   1069         return ERROR;
   1070     }
   1071 
   1072     /**
   1073      * Sets the speech pitch for the TextToSpeech engine.
   1074      *
   1075      * This has no effect on any pre-recorded speech.
   1076      *
   1077      * @param pitch Speech pitch. {@code 1.0} is the normal pitch,
   1078      *            lower values lower the tone of the synthesized voice,
   1079      *            greater values increase it.
   1080      *
   1081      * @return {@link #ERROR} or {@link #SUCCESS}.
   1082      */
   1083     public int setPitch(float pitch) {
   1084         if (pitch > 0.0f) {
   1085             int intPitch = (int)(pitch * 100);
   1086             if (intPitch > 0) {
   1087                 synchronized (mStartLock) {
   1088                     mParams.putInt(Engine.KEY_PARAM_PITCH, intPitch);
   1089                 }
   1090                 return SUCCESS;
   1091             }
   1092         }
   1093         return ERROR;
   1094     }
   1095 
   1096     /**
   1097      * @return the engine currently in use by this TextToSpeech instance.
   1098      * @hide
   1099      */
   1100     public String getCurrentEngine() {
   1101         return mCurrentEngine;
   1102     }
   1103 
   1104     /**
   1105      * Returns a Locale instance describing the language currently being used as the default
   1106      * Text-to-speech language.
   1107      *
   1108      * @return language, country (if any) and variant (if any) used by the client stored in a
   1109      *     Locale instance, or {@code null} on error.
   1110      */
   1111     public Locale getDefaultLanguage() {
   1112         return runAction(new Action<Locale>() {
   1113             @Override
   1114             public Locale run(ITextToSpeechService service) throws RemoteException {
   1115                 String[] defaultLanguage = service.getClientDefaultLanguage();
   1116 
   1117                 return new Locale(defaultLanguage[0], defaultLanguage[1], defaultLanguage[2]);
   1118             }
   1119         }, null, "getDefaultLanguage");
   1120     }
   1121 
   1122     /**
   1123      * Sets the text-to-speech language.
   1124      * The TTS engine will try to use the closest match to the specified
   1125      * language as represented by the Locale, but there is no guarantee that the exact same Locale
   1126      * will be used. Use {@link #isLanguageAvailable(Locale)} to check the level of support
   1127      * before choosing the language to use for the next utterances.
   1128      *
   1129      * @param loc The locale describing the language to be used.
   1130      *
   1131      * @return Code indicating the support status for the locale. See {@link #LANG_AVAILABLE},
   1132      *         {@link #LANG_COUNTRY_AVAILABLE}, {@link #LANG_COUNTRY_VAR_AVAILABLE},
   1133      *         {@link #LANG_MISSING_DATA} and {@link #LANG_NOT_SUPPORTED}.
   1134      */
   1135     public int setLanguage(final Locale loc) {
   1136         return runAction(new Action<Integer>() {
   1137             @Override
   1138             public Integer run(ITextToSpeechService service) throws RemoteException {
   1139                 if (loc == null) {
   1140                     return LANG_NOT_SUPPORTED;
   1141                 }
   1142                 String language = null, country = null;
   1143                 try {
   1144                     language = loc.getISO3Language();
   1145                 } catch (MissingResourceException e) {
   1146                     Log.w(TAG, "Couldn't retrieve ISO 639-2/T language code for locale: " + loc, e);
   1147                     return LANG_NOT_SUPPORTED;
   1148                 }
   1149 
   1150                 try {
   1151                     country = loc.getISO3Country();
   1152                 } catch (MissingResourceException e) {
   1153                     Log.w(TAG, "Couldn't retrieve ISO 3166 country code for locale: " + loc, e);
   1154                     return LANG_NOT_SUPPORTED;
   1155                 }
   1156 
   1157                 String variant = loc.getVariant();
   1158 
   1159                 // Check if the language, country, variant are available, and cache
   1160                 // the available parts.
   1161                 // Note that the language is not actually set here, instead it is cached so it
   1162                 // will be associated with all upcoming utterances.
   1163 
   1164                 int result = service.loadLanguage(getCallerIdentity(), language, country, variant);
   1165                 if (result >= LANG_AVAILABLE){
   1166                     if (result < LANG_COUNTRY_VAR_AVAILABLE) {
   1167                         variant = "";
   1168                         if (result < LANG_COUNTRY_AVAILABLE) {
   1169                             country = "";
   1170                         }
   1171                     }
   1172                     mParams.putString(Engine.KEY_PARAM_LANGUAGE, language);
   1173                     mParams.putString(Engine.KEY_PARAM_COUNTRY, country);
   1174                     mParams.putString(Engine.KEY_PARAM_VARIANT, variant);
   1175                 }
   1176                 return result;
   1177             }
   1178         }, LANG_NOT_SUPPORTED, "setLanguage");
   1179     }
   1180 
   1181     /**
   1182      * Returns a Locale instance describing the language currently being used for synthesis
   1183      * requests sent to the TextToSpeech engine.
   1184      *
   1185      * In Android 4.2 and before (API <= 17) this function returns the language that is currently
   1186      * being used by the TTS engine. That is the last language set by this or any other
   1187      * client by a {@link TextToSpeech#setLanguage} call to the same engine.
   1188      *
   1189      * In Android versions after 4.2 this function returns the language that is currently being
   1190      * used for the synthesis requests sent from this client. That is the last language set
   1191      * by a {@link TextToSpeech#setLanguage} call on this instance.
   1192      *
   1193      * @return language, country (if any) and variant (if any) used by the client stored in a
   1194      *     Locale instance, or {@code null} on error.
   1195      */
   1196     public Locale getLanguage() {
   1197         return runAction(new Action<Locale>() {
   1198             @Override
   1199             public Locale run(ITextToSpeechService service) {
   1200                 /* No service call, but we're accessing mParams, hence need for
   1201                    wrapping it as an Action instance */
   1202                 String lang = mParams.getString(Engine.KEY_PARAM_LANGUAGE, "");
   1203                 String country = mParams.getString(Engine.KEY_PARAM_COUNTRY, "");
   1204                 String variant = mParams.getString(Engine.KEY_PARAM_VARIANT, "");
   1205                 return new Locale(lang, country, variant);
   1206             }
   1207         }, null, "getLanguage");
   1208     }
   1209 
   1210     /**
   1211      * Checks if the specified language as represented by the Locale is available and supported.
   1212      *
   1213      * @param loc The Locale describing the language to be used.
   1214      *
   1215      * @return Code indicating the support status for the locale. See {@link #LANG_AVAILABLE},
   1216      *         {@link #LANG_COUNTRY_AVAILABLE}, {@link #LANG_COUNTRY_VAR_AVAILABLE},
   1217      *         {@link #LANG_MISSING_DATA} and {@link #LANG_NOT_SUPPORTED}.
   1218      */
   1219     public int isLanguageAvailable(final Locale loc) {
   1220         return runAction(new Action<Integer>() {
   1221             @Override
   1222             public Integer run(ITextToSpeechService service) throws RemoteException {
   1223                 String language = null, country = null;
   1224 
   1225                 try {
   1226                     language = loc.getISO3Language();
   1227                 } catch (MissingResourceException e) {
   1228                     Log.w(TAG, "Couldn't retrieve ISO 639-2/T language code for locale: " + loc, e);
   1229                     return LANG_NOT_SUPPORTED;
   1230                 }
   1231 
   1232                 try {
   1233                     country = loc.getISO3Country();
   1234                 } catch (MissingResourceException e) {
   1235                     Log.w(TAG, "Couldn't retrieve ISO 3166 country code for locale: " + loc, e);
   1236                     return LANG_NOT_SUPPORTED;
   1237                 }
   1238 
   1239                 return service.isLanguageAvailable(language, country, loc.getVariant());
   1240             }
   1241         }, LANG_NOT_SUPPORTED, "isLanguageAvailable");
   1242     }
   1243 
   1244     /**
   1245      * Synthesizes the given text to a file using the specified parameters.
   1246      * This method is asynchronous, i.e. the method just adds the request to the queue of TTS
   1247      * requests and then returns. The synthesis might not have finished (or even started!) at the
   1248      * time when this method returns. In order to reliably detect errors during synthesis,
   1249      * we recommend setting an utterance progress listener (see
   1250      * {@link #setOnUtteranceProgressListener}) and using the
   1251      * {@link Engine#KEY_PARAM_UTTERANCE_ID} parameter.
   1252      *
   1253      * @param text The text that should be synthesized. No longer than
   1254      *            {@link #getMaxSpeechInputLength()} characters.
   1255      * @param params Parameters for the request. Can be null.
   1256      *            Supported parameter names:
   1257      *            {@link Engine#KEY_PARAM_UTTERANCE_ID}.
   1258      *            Engine specific parameters may be passed in but the parameter keys
   1259      *            must be prefixed by the name of the engine they are intended for. For example
   1260      *            the keys "com.svox.pico_foo" and "com.svox.pico:bar" will be passed to the
   1261      *            engine named "com.svox.pico" if it is being used.
   1262      * @param filename Absolute file filename to write the generated audio data to.It should be
   1263      *            something like "/sdcard/myappsounds/mysound.wav".
   1264      *
   1265      * @return {@link #ERROR} or {@link #SUCCESS} of <b>queuing</b> the synthesizeToFile operation.
   1266      */
   1267     public int synthesizeToFile(final String text, final HashMap<String, String> params,
   1268             final String filename) {
   1269         return runAction(new Action<Integer>() {
   1270             @Override
   1271             public Integer run(ITextToSpeechService service) throws RemoteException {
   1272                 ParcelFileDescriptor fileDescriptor;
   1273                 int returnValue;
   1274                 try {
   1275                     File file = new File(filename);
   1276                     if(file.exists() && !file.canWrite()) {
   1277                         Log.e(TAG, "Can't write to " + filename);
   1278                         return ERROR;
   1279                     }
   1280                     fileDescriptor = ParcelFileDescriptor.open(file,
   1281                             ParcelFileDescriptor.MODE_WRITE_ONLY |
   1282                             ParcelFileDescriptor.MODE_CREATE |
   1283                             ParcelFileDescriptor.MODE_TRUNCATE);
   1284                     returnValue = service.synthesizeToFileDescriptor(getCallerIdentity(), text,
   1285                             fileDescriptor, getParams(params));
   1286                     fileDescriptor.close();
   1287                     return returnValue;
   1288                 } catch (FileNotFoundException e) {
   1289                     Log.e(TAG, "Opening file " + filename + " failed", e);
   1290                     return ERROR;
   1291                 } catch (IOException e) {
   1292                     Log.e(TAG, "Closing file " + filename + " failed", e);
   1293                     return ERROR;
   1294                 }
   1295             }
   1296         }, ERROR, "synthesizeToFile");
   1297     }
   1298 
   1299     private Bundle getParams(HashMap<String, String> params) {
   1300         if (params != null && !params.isEmpty()) {
   1301             Bundle bundle = new Bundle(mParams);
   1302             copyIntParam(bundle, params, Engine.KEY_PARAM_STREAM);
   1303             copyStringParam(bundle, params, Engine.KEY_PARAM_UTTERANCE_ID);
   1304             copyFloatParam(bundle, params, Engine.KEY_PARAM_VOLUME);
   1305             copyFloatParam(bundle, params, Engine.KEY_PARAM_PAN);
   1306 
   1307             // Copy feature strings defined by the framework.
   1308             copyStringParam(bundle, params, Engine.KEY_FEATURE_NETWORK_SYNTHESIS);
   1309             copyStringParam(bundle, params, Engine.KEY_FEATURE_EMBEDDED_SYNTHESIS);
   1310 
   1311             // Copy over all parameters that start with the name of the
   1312             // engine that we are currently connected to. The engine is
   1313             // free to interpret them as it chooses.
   1314             if (!TextUtils.isEmpty(mCurrentEngine)) {
   1315                 for (Map.Entry<String, String> entry : params.entrySet()) {
   1316                     final String key = entry.getKey();
   1317                     if (key != null && key.startsWith(mCurrentEngine)) {
   1318                         bundle.putString(key, entry.getValue());
   1319                     }
   1320                 }
   1321             }
   1322 
   1323             return bundle;
   1324         } else {
   1325             return mParams;
   1326         }
   1327     }
   1328 
   1329     private void copyStringParam(Bundle bundle, HashMap<String, String> params, String key) {
   1330         String value = params.get(key);
   1331         if (value != null) {
   1332             bundle.putString(key, value);
   1333         }
   1334     }
   1335 
   1336     private void copyIntParam(Bundle bundle, HashMap<String, String> params, String key) {
   1337         String valueString = params.get(key);
   1338         if (!TextUtils.isEmpty(valueString)) {
   1339             try {
   1340                 int value = Integer.parseInt(valueString);
   1341                 bundle.putInt(key, value);
   1342             } catch (NumberFormatException ex) {
   1343                 // don't set the value in the bundle
   1344             }
   1345         }
   1346     }
   1347 
   1348     private void copyFloatParam(Bundle bundle, HashMap<String, String> params, String key) {
   1349         String valueString = params.get(key);
   1350         if (!TextUtils.isEmpty(valueString)) {
   1351             try {
   1352                 float value = Float.parseFloat(valueString);
   1353                 bundle.putFloat(key, value);
   1354             } catch (NumberFormatException ex) {
   1355                 // don't set the value in the bundle
   1356             }
   1357         }
   1358     }
   1359 
   1360     /**
   1361      * Sets the listener that will be notified when synthesis of an utterance completes.
   1362      *
   1363      * @param listener The listener to use.
   1364      *
   1365      * @return {@link #ERROR} or {@link #SUCCESS}.
   1366      *
   1367      * @deprecated Use {@link #setOnUtteranceProgressListener(UtteranceProgressListener)}
   1368      *        instead.
   1369      */
   1370     @Deprecated
   1371     public int setOnUtteranceCompletedListener(final OnUtteranceCompletedListener listener) {
   1372         mUtteranceProgressListener = UtteranceProgressListener.from(listener);
   1373         return TextToSpeech.SUCCESS;
   1374     }
   1375 
   1376     /**
   1377      * Sets the listener that will be notified of various events related to the
   1378      * synthesis of a given utterance.
   1379      *
   1380      * See {@link UtteranceProgressListener} and
   1381      * {@link TextToSpeech.Engine#KEY_PARAM_UTTERANCE_ID}.
   1382      *
   1383      * @param listener the listener to use.
   1384      * @return {@link #ERROR} or {@link #SUCCESS}
   1385      */
   1386     public int setOnUtteranceProgressListener(UtteranceProgressListener listener) {
   1387         mUtteranceProgressListener = listener;
   1388         return TextToSpeech.SUCCESS;
   1389     }
   1390 
   1391     /**
   1392      * Sets the TTS engine to use.
   1393      *
   1394      * @deprecated This doesn't inform callers when the TTS engine has been
   1395      *        initialized. {@link #TextToSpeech(Context, OnInitListener, String)}
   1396      *        can be used with the appropriate engine name. Also, there is no
   1397      *        guarantee that the engine specified will be loaded. If it isn't
   1398      *        installed or disabled, the user / system wide defaults will apply.
   1399      *
   1400      * @param enginePackageName The package name for the synthesis engine (e.g. "com.svox.pico")
   1401      *
   1402      * @return {@link #ERROR} or {@link #SUCCESS}.
   1403      */
   1404     @Deprecated
   1405     public int setEngineByPackageName(String enginePackageName) {
   1406         mRequestedEngine = enginePackageName;
   1407         return initTts();
   1408     }
   1409 
   1410     /**
   1411      * Gets the package name of the default speech synthesis engine.
   1412      *
   1413      * @return Package name of the TTS engine that the user has chosen
   1414      *        as their default.
   1415      */
   1416     public String getDefaultEngine() {
   1417         return mEnginesHelper.getDefaultEngine();
   1418     }
   1419 
   1420     /**
   1421      * Checks whether the user's settings should override settings requested
   1422      * by the calling application. As of the Ice cream sandwich release,
   1423      * user settings never forcibly override the app's settings.
   1424      */
   1425     public boolean areDefaultsEnforced() {
   1426         return false;
   1427     }
   1428 
   1429     /**
   1430      * Gets a list of all installed TTS engines.
   1431      *
   1432      * @return A list of engine info objects. The list can be empty, but never {@code null}.
   1433      */
   1434     public List<EngineInfo> getEngines() {
   1435         return mEnginesHelper.getEngines();
   1436     }
   1437 
   1438     private class Connection implements ServiceConnection {
   1439         private ITextToSpeechService mService;
   1440 
   1441         private SetupConnectionAsyncTask mOnSetupConnectionAsyncTask;
   1442 
   1443         private boolean mEstablished;
   1444 
   1445         private final ITextToSpeechCallback.Stub mCallback = new ITextToSpeechCallback.Stub() {
   1446             @Override
   1447             public void onDone(String utteranceId) {
   1448                 UtteranceProgressListener listener = mUtteranceProgressListener;
   1449                 if (listener != null) {
   1450                     listener.onDone(utteranceId);
   1451                 }
   1452             }
   1453 
   1454             @Override
   1455             public void onError(String utteranceId) {
   1456                 UtteranceProgressListener listener = mUtteranceProgressListener;
   1457                 if (listener != null) {
   1458                     listener.onError(utteranceId);
   1459                 }
   1460             }
   1461 
   1462             @Override
   1463             public void onStart(String utteranceId) {
   1464                 UtteranceProgressListener listener = mUtteranceProgressListener;
   1465                 if (listener != null) {
   1466                     listener.onStart(utteranceId);
   1467                 }
   1468             }
   1469         };
   1470 
   1471         private class SetupConnectionAsyncTask extends AsyncTask<Void, Void, Integer> {
   1472             private final ComponentName mName;
   1473 
   1474             public SetupConnectionAsyncTask(ComponentName name) {
   1475                 mName = name;
   1476             }
   1477 
   1478             @Override
   1479             protected Integer doInBackground(Void... params) {
   1480                 synchronized(mStartLock) {
   1481                     if (isCancelled()) {
   1482                         return null;
   1483                     }
   1484 
   1485                     try {
   1486                         mService.setCallback(getCallerIdentity(), mCallback);
   1487                         String[] defaultLanguage = mService.getClientDefaultLanguage();
   1488 
   1489                         mParams.putString(Engine.KEY_PARAM_LANGUAGE, defaultLanguage[0]);
   1490                         mParams.putString(Engine.KEY_PARAM_COUNTRY, defaultLanguage[1]);
   1491                         mParams.putString(Engine.KEY_PARAM_VARIANT, defaultLanguage[2]);
   1492 
   1493                         Log.i(TAG, "Set up connection to " + mName);
   1494                         return SUCCESS;
   1495                     } catch (RemoteException re) {
   1496                         Log.e(TAG, "Error connecting to service, setCallback() failed");
   1497                         return ERROR;
   1498                     }
   1499                 }
   1500             }
   1501 
   1502             @Override
   1503             protected void onPostExecute(Integer result) {
   1504                 synchronized(mStartLock) {
   1505                     if (mOnSetupConnectionAsyncTask == this) {
   1506                         mOnSetupConnectionAsyncTask = null;
   1507                     }
   1508                     mEstablished = true;
   1509                     dispatchOnInit(result);
   1510                 }
   1511             }
   1512         }
   1513 
   1514         @Override
   1515         public void onServiceConnected(ComponentName name, IBinder service) {
   1516             synchronized(mStartLock) {
   1517                 mConnectingServiceConnection = null;
   1518 
   1519                 Log.i(TAG, "Connected to " + name);
   1520 
   1521                 if (mOnSetupConnectionAsyncTask != null) {
   1522                     mOnSetupConnectionAsyncTask.cancel(false);
   1523                 }
   1524 
   1525                 mService = ITextToSpeechService.Stub.asInterface(service);
   1526                 mServiceConnection = Connection.this;
   1527 
   1528                 mEstablished = false;
   1529                 mOnSetupConnectionAsyncTask = new SetupConnectionAsyncTask(name);
   1530                 mOnSetupConnectionAsyncTask.execute();
   1531             }
   1532         }
   1533 
   1534         public IBinder getCallerIdentity() {
   1535             return mCallback;
   1536         }
   1537 
   1538         /**
   1539          * Clear connection related fields and cancel mOnServiceConnectedAsyncTask if set.
   1540          *
   1541          * @return true if we cancel mOnSetupConnectionAsyncTask in progress.
   1542          */
   1543         private boolean clearServiceConnection() {
   1544             synchronized(mStartLock) {
   1545                 boolean result = false;
   1546                 if (mOnSetupConnectionAsyncTask != null) {
   1547                     result = mOnSetupConnectionAsyncTask.cancel(false);
   1548                     mOnSetupConnectionAsyncTask = null;
   1549                 }
   1550 
   1551                 mService = null;
   1552                 // If this is the active connection, clear it
   1553                 if (mServiceConnection == this) {
   1554                     mServiceConnection = null;
   1555                 }
   1556                 return result;
   1557             }
   1558         }
   1559 
   1560         @Override
   1561         public void onServiceDisconnected(ComponentName name) {
   1562             Log.i(TAG, "Asked to disconnect from " + name);
   1563             if (clearServiceConnection()) {
   1564                 /* We need to protect against a rare case where engine
   1565                  * dies just after successful connection - and we process onServiceDisconnected
   1566                  * before OnServiceConnectedAsyncTask.onPostExecute. onServiceDisconnected cancels
   1567                  * OnServiceConnectedAsyncTask.onPostExecute and we don't call dispatchOnInit
   1568                  * with ERROR as argument.
   1569                  */
   1570                 dispatchOnInit(ERROR);
   1571             }
   1572         }
   1573 
   1574         public void disconnect() {
   1575             mContext.unbindService(this);
   1576             clearServiceConnection();
   1577         }
   1578 
   1579         public boolean isEstablished() {
   1580             return mService != null && mEstablished;
   1581         }
   1582 
   1583         public <R> R runAction(Action<R> action, R errorResult, String method,
   1584                 boolean reconnect, boolean onlyEstablishedConnection) {
   1585             synchronized (mStartLock) {
   1586                 try {
   1587                     if (mService == null) {
   1588                         Log.w(TAG, method + " failed: not connected to TTS engine");
   1589                         return errorResult;
   1590                     }
   1591                     if (onlyEstablishedConnection && !isEstablished()) {
   1592                         Log.w(TAG, method + " failed: TTS engine connection not fully set up");
   1593                         return errorResult;
   1594                     }
   1595                     return action.run(mService);
   1596                 } catch (RemoteException ex) {
   1597                     Log.e(TAG, method + " failed", ex);
   1598                     if (reconnect) {
   1599                         disconnect();
   1600                         initTts();
   1601                     }
   1602                     return errorResult;
   1603                 }
   1604             }
   1605         }
   1606     }
   1607 
   1608     private interface Action<R> {
   1609         R run(ITextToSpeechService service) throws RemoteException;
   1610     }
   1611 
   1612     /**
   1613      * Information about an installed text-to-speech engine.
   1614      *
   1615      * @see TextToSpeech#getEngines
   1616      */
   1617     public static class EngineInfo {
   1618         /**
   1619          * Engine package name..
   1620          */
   1621         public String name;
   1622         /**
   1623          * Localized label for the engine.
   1624          */
   1625         public String label;
   1626         /**
   1627          * Icon for the engine.
   1628          */
   1629         public int icon;
   1630         /**
   1631          * Whether this engine is a part of the system
   1632          * image.
   1633          *
   1634          * @hide
   1635          */
   1636         public boolean system;
   1637         /**
   1638          * The priority the engine declares for the the intent filter
   1639          * {@code android.intent.action.TTS_SERVICE}
   1640          *
   1641          * @hide
   1642          */
   1643         public int priority;
   1644 
   1645         @Override
   1646         public String toString() {
   1647             return "EngineInfo{name=" + name + "}";
   1648         }
   1649 
   1650     }
   1651 
   1652     /**
   1653      * Limit of length of input string passed to speak and synthesizeToFile.
   1654      *
   1655      * @see #speak
   1656      * @see #synthesizeToFile
   1657      */
   1658     public static int getMaxSpeechInputLength() {
   1659         return 4000;
   1660     }
   1661 }
   1662