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 = service.getFeaturesForLanguage(
    997                         locale.getISO3Language(), locale.getISO3Country(), locale.getVariant());
    998                 if (features != null) {
    999                     final Set<String> featureSet = new HashSet<String>();
   1000                     Collections.addAll(featureSet, features);
   1001                     return featureSet;
   1002                 }
   1003                 return null;
   1004             }
   1005         }, null, "getFeatures");
   1006     }
   1007 
   1008     /**
   1009      * Checks whether the TTS engine is busy speaking. Note that a speech item is
   1010      * considered complete once it's audio data has been sent to the audio mixer, or
   1011      * written to a file. There might be a finite lag between this point, and when
   1012      * the audio hardware completes playback.
   1013      *
   1014      * @return {@code true} if the TTS engine is speaking.
   1015      */
   1016     public boolean isSpeaking() {
   1017         return runAction(new Action<Boolean>() {
   1018             @Override
   1019             public Boolean run(ITextToSpeechService service) throws RemoteException {
   1020                 return service.isSpeaking();
   1021             }
   1022         }, false, "isSpeaking");
   1023     }
   1024 
   1025     /**
   1026      * Interrupts the current utterance (whether played or rendered to file) and discards other
   1027      * utterances in the queue.
   1028      *
   1029      * @return {@link #ERROR} or {@link #SUCCESS}.
   1030      */
   1031     public int stop() {
   1032         return runAction(new Action<Integer>() {
   1033             @Override
   1034             public Integer run(ITextToSpeechService service) throws RemoteException {
   1035                 return service.stop(getCallerIdentity());
   1036             }
   1037         }, ERROR, "stop");
   1038     }
   1039 
   1040     /**
   1041      * Sets the speech rate.
   1042      *
   1043      * This has no effect on any pre-recorded speech.
   1044      *
   1045      * @param speechRate Speech rate. {@code 1.0} is the normal speech rate,
   1046      *            lower values slow down the speech ({@code 0.5} is half the normal speech rate),
   1047      *            greater values accelerate it ({@code 2.0} is twice the normal speech rate).
   1048      *
   1049      * @return {@link #ERROR} or {@link #SUCCESS}.
   1050      */
   1051     public int setSpeechRate(float speechRate) {
   1052         if (speechRate > 0.0f) {
   1053             int intRate = (int)(speechRate * 100);
   1054             if (intRate > 0) {
   1055                 synchronized (mStartLock) {
   1056                     mParams.putInt(Engine.KEY_PARAM_RATE, intRate);
   1057                 }
   1058                 return SUCCESS;
   1059             }
   1060         }
   1061         return ERROR;
   1062     }
   1063 
   1064     /**
   1065      * Sets the speech pitch for the TextToSpeech engine.
   1066      *
   1067      * This has no effect on any pre-recorded speech.
   1068      *
   1069      * @param pitch Speech pitch. {@code 1.0} is the normal pitch,
   1070      *            lower values lower the tone of the synthesized voice,
   1071      *            greater values increase it.
   1072      *
   1073      * @return {@link #ERROR} or {@link #SUCCESS}.
   1074      */
   1075     public int setPitch(float pitch) {
   1076         if (pitch > 0.0f) {
   1077             int intPitch = (int)(pitch * 100);
   1078             if (intPitch > 0) {
   1079                 synchronized (mStartLock) {
   1080                     mParams.putInt(Engine.KEY_PARAM_PITCH, intPitch);
   1081                 }
   1082                 return SUCCESS;
   1083             }
   1084         }
   1085         return ERROR;
   1086     }
   1087 
   1088     /**
   1089      * @return the engine currently in use by this TextToSpeech instance.
   1090      * @hide
   1091      */
   1092     public String getCurrentEngine() {
   1093         return mCurrentEngine;
   1094     }
   1095 
   1096     /**
   1097      * Returns a Locale instance describing the language currently being used as the default
   1098      * Text-to-speech language.
   1099      *
   1100      * @return language, country (if any) and variant (if any) used by the client stored in a
   1101      *     Locale instance, or {@code null} on error.
   1102      */
   1103     public Locale getDefaultLanguage() {
   1104         return runAction(new Action<Locale>() {
   1105             @Override
   1106             public Locale run(ITextToSpeechService service) throws RemoteException {
   1107                 String[] defaultLanguage = service.getClientDefaultLanguage();
   1108 
   1109                 return new Locale(defaultLanguage[0], defaultLanguage[1], defaultLanguage[2]);
   1110             }
   1111         }, null, "getDefaultLanguage");
   1112     }
   1113 
   1114     /**
   1115      * Sets the text-to-speech language.
   1116      * The TTS engine will try to use the closest match to the specified
   1117      * language as represented by the Locale, but there is no guarantee that the exact same Locale
   1118      * will be used. Use {@link #isLanguageAvailable(Locale)} to check the level of support
   1119      * before choosing the language to use for the next utterances.
   1120      *
   1121      * @param loc The locale describing the language to be used.
   1122      *
   1123      * @return Code indicating the support status for the locale. See {@link #LANG_AVAILABLE},
   1124      *         {@link #LANG_COUNTRY_AVAILABLE}, {@link #LANG_COUNTRY_VAR_AVAILABLE},
   1125      *         {@link #LANG_MISSING_DATA} and {@link #LANG_NOT_SUPPORTED}.
   1126      */
   1127     public int setLanguage(final Locale loc) {
   1128         return runAction(new Action<Integer>() {
   1129             @Override
   1130             public Integer run(ITextToSpeechService service) throws RemoteException {
   1131                 if (loc == null) {
   1132                     return LANG_NOT_SUPPORTED;
   1133                 }
   1134                 String language = null, country = null;
   1135                 try {
   1136                     language = loc.getISO3Language();
   1137                 } catch (MissingResourceException e) {
   1138                     Log.w(TAG, "Couldn't retrieve ISO 639-2/T language code for locale: " + loc, e);
   1139                     return LANG_NOT_SUPPORTED;
   1140                 }
   1141 
   1142                 try {
   1143                     country = loc.getISO3Country();
   1144                 } catch (MissingResourceException e) {
   1145                     Log.w(TAG, "Couldn't retrieve ISO 3166 country code for locale: " + loc, e);
   1146                     return LANG_NOT_SUPPORTED;
   1147                 }
   1148 
   1149                 String variant = loc.getVariant();
   1150 
   1151                 // Check if the language, country, variant are available, and cache
   1152                 // the available parts.
   1153                 // Note that the language is not actually set here, instead it is cached so it
   1154                 // will be associated with all upcoming utterances.
   1155 
   1156                 int result = service.loadLanguage(getCallerIdentity(), language, country, variant);
   1157                 if (result >= LANG_AVAILABLE){
   1158                     if (result < LANG_COUNTRY_VAR_AVAILABLE) {
   1159                         variant = "";
   1160                         if (result < LANG_COUNTRY_AVAILABLE) {
   1161                             country = "";
   1162                         }
   1163                     }
   1164                     mParams.putString(Engine.KEY_PARAM_LANGUAGE, language);
   1165                     mParams.putString(Engine.KEY_PARAM_COUNTRY, country);
   1166                     mParams.putString(Engine.KEY_PARAM_VARIANT, variant);
   1167                 }
   1168                 return result;
   1169             }
   1170         }, LANG_NOT_SUPPORTED, "setLanguage");
   1171     }
   1172 
   1173     /**
   1174      * Returns a Locale instance describing the language currently being used for synthesis
   1175      * requests sent to the TextToSpeech engine.
   1176      *
   1177      * In Android 4.2 and before (API <= 17) this function returns the language that is currently
   1178      * being used by the TTS engine. That is the last language set by this or any other
   1179      * client by a {@link TextToSpeech#setLanguage} call to the same engine.
   1180      *
   1181      * In Android versions after 4.2 this function returns the language that is currently being
   1182      * used for the synthesis requests sent from this client. That is the last language set
   1183      * by a {@link TextToSpeech#setLanguage} call on this instance.
   1184      *
   1185      * @return language, country (if any) and variant (if any) used by the client stored in a
   1186      *     Locale instance, or {@code null} on error.
   1187      */
   1188     public Locale getLanguage() {
   1189         return runAction(new Action<Locale>() {
   1190             @Override
   1191             public Locale run(ITextToSpeechService service) {
   1192                 /* No service call, but we're accessing mParams, hence need for
   1193                    wrapping it as an Action instance */
   1194                 String lang = mParams.getString(Engine.KEY_PARAM_LANGUAGE, "");
   1195                 String country = mParams.getString(Engine.KEY_PARAM_COUNTRY, "");
   1196                 String variant = mParams.getString(Engine.KEY_PARAM_VARIANT, "");
   1197                 return new Locale(lang, country, variant);
   1198             }
   1199         }, null, "getLanguage");
   1200     }
   1201 
   1202     /**
   1203      * Checks if the specified language as represented by the Locale is available and supported.
   1204      *
   1205      * @param loc The Locale describing the language to be used.
   1206      *
   1207      * @return Code indicating the support status for the locale. See {@link #LANG_AVAILABLE},
   1208      *         {@link #LANG_COUNTRY_AVAILABLE}, {@link #LANG_COUNTRY_VAR_AVAILABLE},
   1209      *         {@link #LANG_MISSING_DATA} and {@link #LANG_NOT_SUPPORTED}.
   1210      */
   1211     public int isLanguageAvailable(final Locale loc) {
   1212         return runAction(new Action<Integer>() {
   1213             @Override
   1214             public Integer run(ITextToSpeechService service) throws RemoteException {
   1215                 String language = null, country = null;
   1216 
   1217                 try {
   1218                     language = loc.getISO3Language();
   1219                 } catch (MissingResourceException e) {
   1220                     Log.w(TAG, "Couldn't retrieve ISO 639-2/T language code for locale: " + loc, e);
   1221                     return LANG_NOT_SUPPORTED;
   1222                 }
   1223 
   1224                 try {
   1225                     country = loc.getISO3Country();
   1226                 } catch (MissingResourceException e) {
   1227                     Log.w(TAG, "Couldn't retrieve ISO 3166 country code for locale: " + loc, e);
   1228                     return LANG_NOT_SUPPORTED;
   1229                 }
   1230 
   1231                 return service.isLanguageAvailable(language, country, loc.getVariant());
   1232             }
   1233         }, LANG_NOT_SUPPORTED, "isLanguageAvailable");
   1234     }
   1235 
   1236     /**
   1237      * Synthesizes the given text to a file using the specified parameters.
   1238      * This method is asynchronous, i.e. the method just adds the request to the queue of TTS
   1239      * requests and then returns. The synthesis might not have finished (or even started!) at the
   1240      * time when this method returns. In order to reliably detect errors during synthesis,
   1241      * we recommend setting an utterance progress listener (see
   1242      * {@link #setOnUtteranceProgressListener}) and using the
   1243      * {@link Engine#KEY_PARAM_UTTERANCE_ID} parameter.
   1244      *
   1245      * @param text The text that should be synthesized. No longer than
   1246      *            {@link #getMaxSpeechInputLength()} characters.
   1247      * @param params Parameters for the request. Can be null.
   1248      *            Supported parameter names:
   1249      *            {@link Engine#KEY_PARAM_UTTERANCE_ID}.
   1250      *            Engine specific parameters may be passed in but the parameter keys
   1251      *            must be prefixed by the name of the engine they are intended for. For example
   1252      *            the keys "com.svox.pico_foo" and "com.svox.pico:bar" will be passed to the
   1253      *            engine named "com.svox.pico" if it is being used.
   1254      * @param filename Absolute file filename to write the generated audio data to.It should be
   1255      *            something like "/sdcard/myappsounds/mysound.wav".
   1256      *
   1257      * @return {@link #ERROR} or {@link #SUCCESS} of <b>queuing</b> the synthesizeToFile operation.
   1258      */
   1259     public int synthesizeToFile(final String text, final HashMap<String, String> params,
   1260             final String filename) {
   1261         return runAction(new Action<Integer>() {
   1262             @Override
   1263             public Integer run(ITextToSpeechService service) throws RemoteException {
   1264                 ParcelFileDescriptor fileDescriptor;
   1265                 int returnValue;
   1266                 try {
   1267                     File file = new File(filename);
   1268                     if(file.exists() && !file.canWrite()) {
   1269                         Log.e(TAG, "Can't write to " + filename);
   1270                         return ERROR;
   1271                     }
   1272                     fileDescriptor = ParcelFileDescriptor.open(file,
   1273                             ParcelFileDescriptor.MODE_WRITE_ONLY |
   1274                             ParcelFileDescriptor.MODE_CREATE |
   1275                             ParcelFileDescriptor.MODE_TRUNCATE);
   1276                     returnValue = service.synthesizeToFileDescriptor(getCallerIdentity(), text,
   1277                             fileDescriptor, getParams(params));
   1278                     fileDescriptor.close();
   1279                     return returnValue;
   1280                 } catch (FileNotFoundException e) {
   1281                     Log.e(TAG, "Opening file " + filename + " failed", e);
   1282                     return ERROR;
   1283                 } catch (IOException e) {
   1284                     Log.e(TAG, "Closing file " + filename + " failed", e);
   1285                     return ERROR;
   1286                 }
   1287             }
   1288         }, ERROR, "synthesizeToFile");
   1289     }
   1290 
   1291     private Bundle getParams(HashMap<String, String> params) {
   1292         if (params != null && !params.isEmpty()) {
   1293             Bundle bundle = new Bundle(mParams);
   1294             copyIntParam(bundle, params, Engine.KEY_PARAM_STREAM);
   1295             copyStringParam(bundle, params, Engine.KEY_PARAM_UTTERANCE_ID);
   1296             copyFloatParam(bundle, params, Engine.KEY_PARAM_VOLUME);
   1297             copyFloatParam(bundle, params, Engine.KEY_PARAM_PAN);
   1298 
   1299             // Copy feature strings defined by the framework.
   1300             copyStringParam(bundle, params, Engine.KEY_FEATURE_NETWORK_SYNTHESIS);
   1301             copyStringParam(bundle, params, Engine.KEY_FEATURE_EMBEDDED_SYNTHESIS);
   1302 
   1303             // Copy over all parameters that start with the name of the
   1304             // engine that we are currently connected to. The engine is
   1305             // free to interpret them as it chooses.
   1306             if (!TextUtils.isEmpty(mCurrentEngine)) {
   1307                 for (Map.Entry<String, String> entry : params.entrySet()) {
   1308                     final String key = entry.getKey();
   1309                     if (key != null && key.startsWith(mCurrentEngine)) {
   1310                         bundle.putString(key, entry.getValue());
   1311                     }
   1312                 }
   1313             }
   1314 
   1315             return bundle;
   1316         } else {
   1317             return mParams;
   1318         }
   1319     }
   1320 
   1321     private void copyStringParam(Bundle bundle, HashMap<String, String> params, String key) {
   1322         String value = params.get(key);
   1323         if (value != null) {
   1324             bundle.putString(key, value);
   1325         }
   1326     }
   1327 
   1328     private void copyIntParam(Bundle bundle, HashMap<String, String> params, String key) {
   1329         String valueString = params.get(key);
   1330         if (!TextUtils.isEmpty(valueString)) {
   1331             try {
   1332                 int value = Integer.parseInt(valueString);
   1333                 bundle.putInt(key, value);
   1334             } catch (NumberFormatException ex) {
   1335                 // don't set the value in the bundle
   1336             }
   1337         }
   1338     }
   1339 
   1340     private void copyFloatParam(Bundle bundle, HashMap<String, String> params, String key) {
   1341         String valueString = params.get(key);
   1342         if (!TextUtils.isEmpty(valueString)) {
   1343             try {
   1344                 float value = Float.parseFloat(valueString);
   1345                 bundle.putFloat(key, value);
   1346             } catch (NumberFormatException ex) {
   1347                 // don't set the value in the bundle
   1348             }
   1349         }
   1350     }
   1351 
   1352     /**
   1353      * Sets the listener that will be notified when synthesis of an utterance completes.
   1354      *
   1355      * @param listener The listener to use.
   1356      *
   1357      * @return {@link #ERROR} or {@link #SUCCESS}.
   1358      *
   1359      * @deprecated Use {@link #setOnUtteranceProgressListener(UtteranceProgressListener)}
   1360      *        instead.
   1361      */
   1362     @Deprecated
   1363     public int setOnUtteranceCompletedListener(final OnUtteranceCompletedListener listener) {
   1364         mUtteranceProgressListener = UtteranceProgressListener.from(listener);
   1365         return TextToSpeech.SUCCESS;
   1366     }
   1367 
   1368     /**
   1369      * Sets the listener that will be notified of various events related to the
   1370      * synthesis of a given utterance.
   1371      *
   1372      * See {@link UtteranceProgressListener} and
   1373      * {@link TextToSpeech.Engine#KEY_PARAM_UTTERANCE_ID}.
   1374      *
   1375      * @param listener the listener to use.
   1376      * @return {@link #ERROR} or {@link #SUCCESS}
   1377      */
   1378     public int setOnUtteranceProgressListener(UtteranceProgressListener listener) {
   1379         mUtteranceProgressListener = listener;
   1380         return TextToSpeech.SUCCESS;
   1381     }
   1382 
   1383     /**
   1384      * Sets the TTS engine to use.
   1385      *
   1386      * @deprecated This doesn't inform callers when the TTS engine has been
   1387      *        initialized. {@link #TextToSpeech(Context, OnInitListener, String)}
   1388      *        can be used with the appropriate engine name. Also, there is no
   1389      *        guarantee that the engine specified will be loaded. If it isn't
   1390      *        installed or disabled, the user / system wide defaults will apply.
   1391      *
   1392      * @param enginePackageName The package name for the synthesis engine (e.g. "com.svox.pico")
   1393      *
   1394      * @return {@link #ERROR} or {@link #SUCCESS}.
   1395      */
   1396     @Deprecated
   1397     public int setEngineByPackageName(String enginePackageName) {
   1398         mRequestedEngine = enginePackageName;
   1399         return initTts();
   1400     }
   1401 
   1402     /**
   1403      * Gets the package name of the default speech synthesis engine.
   1404      *
   1405      * @return Package name of the TTS engine that the user has chosen
   1406      *        as their default.
   1407      */
   1408     public String getDefaultEngine() {
   1409         return mEnginesHelper.getDefaultEngine();
   1410     }
   1411 
   1412     /**
   1413      * Checks whether the user's settings should override settings requested
   1414      * by the calling application. As of the Ice cream sandwich release,
   1415      * user settings never forcibly override the app's settings.
   1416      */
   1417     public boolean areDefaultsEnforced() {
   1418         return false;
   1419     }
   1420 
   1421     /**
   1422      * Gets a list of all installed TTS engines.
   1423      *
   1424      * @return A list of engine info objects. The list can be empty, but never {@code null}.
   1425      */
   1426     public List<EngineInfo> getEngines() {
   1427         return mEnginesHelper.getEngines();
   1428     }
   1429 
   1430     private class Connection implements ServiceConnection {
   1431         private ITextToSpeechService mService;
   1432 
   1433         private SetupConnectionAsyncTask mOnSetupConnectionAsyncTask;
   1434 
   1435         private boolean mEstablished;
   1436 
   1437         private final ITextToSpeechCallback.Stub mCallback = new ITextToSpeechCallback.Stub() {
   1438             @Override
   1439             public void onDone(String utteranceId) {
   1440                 UtteranceProgressListener listener = mUtteranceProgressListener;
   1441                 if (listener != null) {
   1442                     listener.onDone(utteranceId);
   1443                 }
   1444             }
   1445 
   1446             @Override
   1447             public void onError(String utteranceId) {
   1448                 UtteranceProgressListener listener = mUtteranceProgressListener;
   1449                 if (listener != null) {
   1450                     listener.onError(utteranceId);
   1451                 }
   1452             }
   1453 
   1454             @Override
   1455             public void onStart(String utteranceId) {
   1456                 UtteranceProgressListener listener = mUtteranceProgressListener;
   1457                 if (listener != null) {
   1458                     listener.onStart(utteranceId);
   1459                 }
   1460             }
   1461         };
   1462 
   1463         private class SetupConnectionAsyncTask extends AsyncTask<Void, Void, Integer> {
   1464             private final ComponentName mName;
   1465 
   1466             public SetupConnectionAsyncTask(ComponentName name) {
   1467                 mName = name;
   1468             }
   1469 
   1470             @Override
   1471             protected Integer doInBackground(Void... params) {
   1472                 synchronized(mStartLock) {
   1473                     if (isCancelled()) {
   1474                         return null;
   1475                     }
   1476 
   1477                     try {
   1478                         mService.setCallback(getCallerIdentity(), mCallback);
   1479                         String[] defaultLanguage = mService.getClientDefaultLanguage();
   1480 
   1481                         mParams.putString(Engine.KEY_PARAM_LANGUAGE, defaultLanguage[0]);
   1482                         mParams.putString(Engine.KEY_PARAM_COUNTRY, defaultLanguage[1]);
   1483                         mParams.putString(Engine.KEY_PARAM_VARIANT, defaultLanguage[2]);
   1484 
   1485                         Log.i(TAG, "Set up connection to " + mName);
   1486                         return SUCCESS;
   1487                     } catch (RemoteException re) {
   1488                         Log.e(TAG, "Error connecting to service, setCallback() failed");
   1489                         return ERROR;
   1490                     }
   1491                 }
   1492             }
   1493 
   1494             @Override
   1495             protected void onPostExecute(Integer result) {
   1496                 synchronized(mStartLock) {
   1497                     if (mOnSetupConnectionAsyncTask == this) {
   1498                         mOnSetupConnectionAsyncTask = null;
   1499                     }
   1500                     mEstablished = true;
   1501                     dispatchOnInit(result);
   1502                 }
   1503             }
   1504         }
   1505 
   1506         @Override
   1507         public void onServiceConnected(ComponentName name, IBinder service) {
   1508             synchronized(mStartLock) {
   1509                 mConnectingServiceConnection = null;
   1510 
   1511                 Log.i(TAG, "Connected to " + name);
   1512 
   1513                 if (mOnSetupConnectionAsyncTask != null) {
   1514                     mOnSetupConnectionAsyncTask.cancel(false);
   1515                 }
   1516 
   1517                 mService = ITextToSpeechService.Stub.asInterface(service);
   1518                 mServiceConnection = Connection.this;
   1519 
   1520                 mEstablished = false;
   1521                 mOnSetupConnectionAsyncTask = new SetupConnectionAsyncTask(name);
   1522                 mOnSetupConnectionAsyncTask.execute();
   1523             }
   1524         }
   1525 
   1526         public IBinder getCallerIdentity() {
   1527             return mCallback;
   1528         }
   1529 
   1530         /**
   1531          * Clear connection related fields and cancel mOnServiceConnectedAsyncTask if set.
   1532          *
   1533          * @return true if we cancel mOnSetupConnectionAsyncTask in progress.
   1534          */
   1535         private boolean clearServiceConnection() {
   1536             synchronized(mStartLock) {
   1537                 boolean result = false;
   1538                 if (mOnSetupConnectionAsyncTask != null) {
   1539                     result = mOnSetupConnectionAsyncTask.cancel(false);
   1540                     mOnSetupConnectionAsyncTask = null;
   1541                 }
   1542 
   1543                 mService = null;
   1544                 // If this is the active connection, clear it
   1545                 if (mServiceConnection == this) {
   1546                     mServiceConnection = null;
   1547                 }
   1548                 return result;
   1549             }
   1550         }
   1551 
   1552         @Override
   1553         public void onServiceDisconnected(ComponentName name) {
   1554             Log.i(TAG, "Asked to disconnect from " + name);
   1555             if (clearServiceConnection()) {
   1556                 /* We need to protect against a rare case where engine
   1557                  * dies just after successful connection - and we process onServiceDisconnected
   1558                  * before OnServiceConnectedAsyncTask.onPostExecute. onServiceDisconnected cancels
   1559                  * OnServiceConnectedAsyncTask.onPostExecute and we don't call dispatchOnInit
   1560                  * with ERROR as argument.
   1561                  */
   1562                 dispatchOnInit(ERROR);
   1563             }
   1564         }
   1565 
   1566         public void disconnect() {
   1567             mContext.unbindService(this);
   1568             clearServiceConnection();
   1569         }
   1570 
   1571         public boolean isEstablished() {
   1572             return mService != null && mEstablished;
   1573         }
   1574 
   1575         public <R> R runAction(Action<R> action, R errorResult, String method,
   1576                 boolean reconnect, boolean onlyEstablishedConnection) {
   1577             synchronized (mStartLock) {
   1578                 try {
   1579                     if (mService == null) {
   1580                         Log.w(TAG, method + " failed: not connected to TTS engine");
   1581                         return errorResult;
   1582                     }
   1583                     if (onlyEstablishedConnection && !isEstablished()) {
   1584                         Log.w(TAG, method + " failed: TTS engine connection not fully set up");
   1585                         return errorResult;
   1586                     }
   1587                     return action.run(mService);
   1588                 } catch (RemoteException ex) {
   1589                     Log.e(TAG, method + " failed", ex);
   1590                     if (reconnect) {
   1591                         disconnect();
   1592                         initTts();
   1593                     }
   1594                     return errorResult;
   1595                 }
   1596             }
   1597         }
   1598     }
   1599 
   1600     private interface Action<R> {
   1601         R run(ITextToSpeechService service) throws RemoteException;
   1602     }
   1603 
   1604     /**
   1605      * Information about an installed text-to-speech engine.
   1606      *
   1607      * @see TextToSpeech#getEngines
   1608      */
   1609     public static class EngineInfo {
   1610         /**
   1611          * Engine package name..
   1612          */
   1613         public String name;
   1614         /**
   1615          * Localized label for the engine.
   1616          */
   1617         public String label;
   1618         /**
   1619          * Icon for the engine.
   1620          */
   1621         public int icon;
   1622         /**
   1623          * Whether this engine is a part of the system
   1624          * image.
   1625          *
   1626          * @hide
   1627          */
   1628         public boolean system;
   1629         /**
   1630          * The priority the engine declares for the the intent filter
   1631          * {@code android.intent.action.TTS_SERVICE}
   1632          *
   1633          * @hide
   1634          */
   1635         public int priority;
   1636 
   1637         @Override
   1638         public String toString() {
   1639             return "EngineInfo{name=" + name + "}";
   1640         }
   1641 
   1642     }
   1643 
   1644     /**
   1645      * Limit of length of input string passed to speak and synthesizeToFile.
   1646      *
   1647      * @see #speak
   1648      * @see #synthesizeToFile
   1649      */
   1650     public static int getMaxSpeechInputLength() {
   1651         return 4000;
   1652     }
   1653 }
   1654