Home | History | Annotate | Download | only in tts
      1 /*
      2  * Copyright (C) 2011 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.NonNull;
     19 import android.app.Service;
     20 import android.content.Intent;
     21 import android.media.AudioAttributes;
     22 import android.media.AudioManager;
     23 import android.net.Uri;
     24 import android.os.Binder;
     25 import android.os.Bundle;
     26 import android.os.Handler;
     27 import android.os.HandlerThread;
     28 import android.os.IBinder;
     29 import android.os.Looper;
     30 import android.os.Message;
     31 import android.os.MessageQueue;
     32 import android.os.ParcelFileDescriptor;
     33 import android.os.RemoteCallbackList;
     34 import android.os.RemoteException;
     35 import android.provider.Settings;
     36 import android.speech.tts.TextToSpeech.Engine;
     37 import android.text.TextUtils;
     38 import android.util.Log;
     39 
     40 import java.io.FileOutputStream;
     41 import java.io.IOException;
     42 import java.util.ArrayList;
     43 import java.util.HashMap;
     44 import java.util.HashSet;
     45 import java.util.List;
     46 import java.util.Locale;
     47 import java.util.MissingResourceException;
     48 import java.util.Set;
     49 
     50 
     51 /**
     52  * Abstract base class for TTS engine implementations. The following methods
     53  * need to be implemented:
     54  * <ul>
     55  * <li>{@link #onIsLanguageAvailable}</li>
     56  * <li>{@link #onLoadLanguage}</li>
     57  * <li>{@link #onGetLanguage}</li>
     58  * <li>{@link #onSynthesizeText}</li>
     59  * <li>{@link #onStop}</li>
     60  * </ul>
     61  * The first three deal primarily with language management, and are used to
     62  * query the engine for it's support for a given language and indicate to it
     63  * that requests in a given language are imminent.
     64  *
     65  * {@link #onSynthesizeText} is central to the engine implementation. The
     66  * implementation should synthesize text as per the request parameters and
     67  * return synthesized data via the supplied callback. This class and its helpers
     68  * will then consume that data, which might mean queuing it for playback or writing
     69  * it to a file or similar. All calls to this method will be on a single thread,
     70  * which will be different from the main thread of the service. Synthesis must be
     71  * synchronous which means the engine must NOT hold on to the callback or call any
     72  * methods on it after the method returns.
     73  *
     74  * {@link #onStop} tells the engine that it should stop
     75  * all ongoing synthesis, if any. Any pending data from the current synthesis
     76  * will be discarded.
     77  *
     78  * {@link #onGetLanguage} is not required as of JELLYBEAN_MR2 (API 18) and later, it is only
     79  * called on earlier versions of Android.
     80  *
     81  * API Level 20 adds support for Voice objects. Voices are an abstraction that allow the TTS
     82  * service to expose multiple backends for a single locale. Each one of them can have a different
     83  * features set. In order to fully take advantage of voices, an engine should implement
     84  * the following methods:
     85  * <ul>
     86  * <li>{@link #onGetVoices()}</li>
     87  * <li>{@link #onIsValidVoiceName(String)}</li>
     88  * <li>{@link #onLoadVoice(String)}</li>
     89  * <li>{@link #onGetDefaultVoiceNameFor(String, String, String)}</li>
     90  * </ul>
     91  * The first three methods are siblings of the {@link #onGetLanguage},
     92  * {@link #onIsLanguageAvailable} and {@link #onLoadLanguage} methods. The last one,
     93  * {@link #onGetDefaultVoiceNameFor(String, String, String)} is a link between locale and voice
     94  * based methods. Since API level 21 {@link TextToSpeech#setLanguage} is implemented by
     95  * calling {@link TextToSpeech#setVoice} with the voice returned by
     96  * {@link #onGetDefaultVoiceNameFor(String, String, String)}.
     97  *
     98  * If the client uses a voice instead of a locale, {@link SynthesisRequest} will contain the
     99  * requested voice name.
    100  *
    101  * The default implementations of Voice-related methods implement them using the
    102  * pre-existing locale-based implementation.
    103  */
    104 public abstract class TextToSpeechService extends Service {
    105 
    106     private static final boolean DBG = false;
    107     private static final String TAG = "TextToSpeechService";
    108 
    109     private static final String SYNTH_THREAD_NAME = "SynthThread";
    110 
    111     private SynthHandler mSynthHandler;
    112     // A thread and it's associated handler for playing back any audio
    113     // associated with this TTS engine. Will handle all requests except synthesis
    114     // to file requests, which occur on the synthesis thread.
    115     @NonNull private AudioPlaybackHandler mAudioPlaybackHandler;
    116     private TtsEngines mEngineHelper;
    117 
    118     private CallbackMap mCallbacks;
    119     private String mPackageName;
    120 
    121     private final Object mVoicesInfoLock = new Object();
    122 
    123     @Override
    124     public void onCreate() {
    125         if (DBG) Log.d(TAG, "onCreate()");
    126         super.onCreate();
    127 
    128         SynthThread synthThread = new SynthThread();
    129         synthThread.start();
    130         mSynthHandler = new SynthHandler(synthThread.getLooper());
    131 
    132         mAudioPlaybackHandler = new AudioPlaybackHandler();
    133         mAudioPlaybackHandler.start();
    134 
    135         mEngineHelper = new TtsEngines(this);
    136 
    137         mCallbacks = new CallbackMap();
    138 
    139         mPackageName = getApplicationInfo().packageName;
    140 
    141         String[] defaultLocale = getSettingsLocale();
    142 
    143         // Load default language
    144         onLoadLanguage(defaultLocale[0], defaultLocale[1], defaultLocale[2]);
    145     }
    146 
    147     @Override
    148     public void onDestroy() {
    149         if (DBG) Log.d(TAG, "onDestroy()");
    150 
    151         // Tell the synthesizer to stop
    152         mSynthHandler.quit();
    153         // Tell the audio playback thread to stop.
    154         mAudioPlaybackHandler.quit();
    155         // Unregister all callbacks.
    156         mCallbacks.kill();
    157 
    158         super.onDestroy();
    159     }
    160 
    161     /**
    162      * Checks whether the engine supports a given language.
    163      *
    164      * Can be called on multiple threads.
    165      *
    166      * Its return values HAVE to be consistent with onLoadLanguage.
    167      *
    168      * @param lang ISO-3 language code.
    169      * @param country ISO-3 country code. May be empty or null.
    170      * @param variant Language variant. May be empty or null.
    171      * @return Code indicating the support status for the locale.
    172      *         One of {@link TextToSpeech#LANG_AVAILABLE},
    173      *         {@link TextToSpeech#LANG_COUNTRY_AVAILABLE},
    174      *         {@link TextToSpeech#LANG_COUNTRY_VAR_AVAILABLE},
    175      *         {@link TextToSpeech#LANG_MISSING_DATA}
    176      *         {@link TextToSpeech#LANG_NOT_SUPPORTED}.
    177      */
    178     protected abstract int onIsLanguageAvailable(String lang, String country, String variant);
    179 
    180     /**
    181      * Returns the language, country and variant currently being used by the TTS engine.
    182      *
    183      * This method will be called only on Android 4.2 and before (API <= 17). In later versions
    184      * this method is not called by the Android TTS framework.
    185      *
    186      * Can be called on multiple threads.
    187      *
    188      * @return A 3-element array, containing language (ISO 3-letter code),
    189      *         country (ISO 3-letter code) and variant used by the engine.
    190      *         The country and variant may be {@code ""}. If country is empty, then variant must
    191      *         be empty too.
    192      * @see Locale#getISO3Language()
    193      * @see Locale#getISO3Country()
    194      * @see Locale#getVariant()
    195      */
    196     protected abstract String[] onGetLanguage();
    197 
    198     /**
    199      * Notifies the engine that it should load a speech synthesis language. There is no guarantee
    200      * that this method is always called before the language is used for synthesis. It is merely
    201      * a hint to the engine that it will probably get some synthesis requests for this language
    202      * at some point in the future.
    203      *
    204      * Can be called on multiple threads.
    205      * In <= Android 4.2 (<= API 17) can be called on main and service binder threads.
    206      * In > Android 4.2 (> API 17) can be called on main and synthesis threads.
    207      *
    208      * @param lang ISO-3 language code.
    209      * @param country ISO-3 country code. May be empty or null.
    210      * @param variant Language variant. May be empty or null.
    211      * @return Code indicating the support status for the locale.
    212      *         One of {@link TextToSpeech#LANG_AVAILABLE},
    213      *         {@link TextToSpeech#LANG_COUNTRY_AVAILABLE},
    214      *         {@link TextToSpeech#LANG_COUNTRY_VAR_AVAILABLE},
    215      *         {@link TextToSpeech#LANG_MISSING_DATA}
    216      *         {@link TextToSpeech#LANG_NOT_SUPPORTED}.
    217      */
    218     protected abstract int onLoadLanguage(String lang, String country, String variant);
    219 
    220     /**
    221      * Notifies the service that it should stop any in-progress speech synthesis.
    222      * This method can be called even if no speech synthesis is currently in progress.
    223      *
    224      * Can be called on multiple threads, but not on the synthesis thread.
    225      */
    226     protected abstract void onStop();
    227 
    228     /**
    229      * Tells the service to synthesize speech from the given text. This method should block until
    230      * the synthesis is finished. Called on the synthesis thread.
    231      *
    232      * @param request The synthesis request.
    233      * @param callback The callback that the engine must use to make data available for playback or
    234      *     for writing to a file.
    235      */
    236     protected abstract void onSynthesizeText(SynthesisRequest request, SynthesisCallback callback);
    237 
    238     /**
    239      * Queries the service for a set of features supported for a given language.
    240      *
    241      * Can be called on multiple threads.
    242      *
    243      * @param lang ISO-3 language code.
    244      * @param country ISO-3 country code. May be empty or null.
    245      * @param variant Language variant. May be empty or null.
    246      * @return A list of features supported for the given language.
    247      */
    248     protected Set<String> onGetFeaturesForLanguage(String lang, String country, String variant) {
    249         return new HashSet<String>();
    250     }
    251 
    252     private int getExpectedLanguageAvailableStatus(Locale locale) {
    253         int expectedStatus = TextToSpeech.LANG_COUNTRY_VAR_AVAILABLE;
    254         if (locale.getVariant().isEmpty()) {
    255             if (locale.getCountry().isEmpty()) {
    256                 expectedStatus = TextToSpeech.LANG_AVAILABLE;
    257             } else {
    258                 expectedStatus = TextToSpeech.LANG_COUNTRY_AVAILABLE;
    259             }
    260         }
    261         return expectedStatus;
    262     }
    263 
    264     /**
    265      * Queries the service for a set of supported voices.
    266      *
    267      * Can be called on multiple threads.
    268      *
    269      * The default implementation tries to enumerate all available locales, pass them to
    270      * {@link #onIsLanguageAvailable(String, String, String)} and create Voice instances (using
    271      * the locale's BCP-47 language tag as the voice name) for the ones that are supported.
    272      * Note, that this implementation is suitable only for engines that don't have multiple voices
    273      * for a single locale. Also, this implementation won't work with Locales not listed in the
    274      * set returned by the {@link Locale#getAvailableLocales()} method.
    275      *
    276      * @return A list of voices supported.
    277      */
    278     public List<Voice> onGetVoices() {
    279         // Enumerate all locales and check if they are available
    280         ArrayList<Voice> voices = new ArrayList<Voice>();
    281         for (Locale locale : Locale.getAvailableLocales()) {
    282             int expectedStatus = getExpectedLanguageAvailableStatus(locale);
    283             try {
    284                 int localeStatus = onIsLanguageAvailable(locale.getISO3Language(),
    285                         locale.getISO3Country(), locale.getVariant());
    286                 if (localeStatus != expectedStatus) {
    287                     continue;
    288                 }
    289             } catch (MissingResourceException e) {
    290                 // Ignore locale without iso 3 codes
    291                 continue;
    292             }
    293             Set<String> features = onGetFeaturesForLanguage(locale.getISO3Language(),
    294                     locale.getISO3Country(), locale.getVariant());
    295             String voiceName = onGetDefaultVoiceNameFor(locale.getISO3Language(),
    296                     locale.getISO3Country(), locale.getVariant());
    297             voices.add(new Voice(voiceName, locale, Voice.QUALITY_NORMAL,
    298                     Voice.LATENCY_NORMAL, false, features));
    299         }
    300         return voices;
    301     }
    302 
    303     /**
    304      * Return a name of the default voice for a given locale.
    305      *
    306      * This method provides a mapping between locales and available voices. This method is
    307      * used in {@link TextToSpeech#setLanguage}, which calls this method and then calls
    308      * {@link TextToSpeech#setVoice} with the voice returned by this method.
    309      *
    310      * Also, it's used by {@link TextToSpeech#getDefaultVoice()} to find a default voice for
    311      * the default locale.
    312      *
    313      * @param lang ISO-3 language code.
    314      * @param country ISO-3 country code. May be empty or null.
    315      * @param variant Language variant. May be empty or null.
    316 
    317      * @return A name of the default voice for a given locale.
    318      */
    319     public String onGetDefaultVoiceNameFor(String lang, String country, String variant) {
    320         int localeStatus = onIsLanguageAvailable(lang, country, variant);
    321         Locale iso3Locale = null;
    322         switch (localeStatus) {
    323             case TextToSpeech.LANG_AVAILABLE:
    324                 iso3Locale = new Locale(lang);
    325                 break;
    326             case TextToSpeech.LANG_COUNTRY_AVAILABLE:
    327                 iso3Locale = new Locale(lang, country);
    328                 break;
    329             case TextToSpeech.LANG_COUNTRY_VAR_AVAILABLE:
    330                 iso3Locale = new Locale(lang, country, variant);
    331                 break;
    332             default:
    333                 return null;
    334         }
    335         Locale properLocale = TtsEngines.normalizeTTSLocale(iso3Locale);
    336         String voiceName = properLocale.toLanguageTag();
    337         if (onIsValidVoiceName(voiceName) == TextToSpeech.SUCCESS) {
    338             return voiceName;
    339         } else {
    340             return null;
    341         }
    342     }
    343 
    344     /**
    345      * Notifies the engine that it should load a speech synthesis voice. There is no guarantee
    346      * that this method is always called before the voice is used for synthesis. It is merely
    347      * a hint to the engine that it will probably get some synthesis requests for this voice
    348      * at some point in the future.
    349      *
    350      * Will be called only on synthesis thread.
    351      *
    352      * The default implementation creates a Locale from the voice name (by interpreting the name as
    353      * a BCP-47 tag for the locale), and passes it to
    354      * {@link #onLoadLanguage(String, String, String)}.
    355      *
    356      * @param voiceName Name of the voice.
    357      * @return {@link TextToSpeech#ERROR} or {@link TextToSpeech#SUCCESS}.
    358      */
    359     public int onLoadVoice(String voiceName) {
    360         Locale locale = Locale.forLanguageTag(voiceName);
    361         if (locale == null) {
    362             return TextToSpeech.ERROR;
    363         }
    364         int expectedStatus = getExpectedLanguageAvailableStatus(locale);
    365         try {
    366             int localeStatus = onIsLanguageAvailable(locale.getISO3Language(),
    367                     locale.getISO3Country(), locale.getVariant());
    368             if (localeStatus != expectedStatus) {
    369                 return TextToSpeech.ERROR;
    370             }
    371             onLoadLanguage(locale.getISO3Language(),
    372                     locale.getISO3Country(), locale.getVariant());
    373             return TextToSpeech.SUCCESS;
    374         } catch (MissingResourceException e) {
    375             return TextToSpeech.ERROR;
    376         }
    377     }
    378 
    379     /**
    380      * Checks whether the engine supports a voice with a given name.
    381      *
    382      * Can be called on multiple threads.
    383      *
    384      * The default implementation treats the voice name as a language tag, creating a Locale from
    385      * the voice name, and passes it to {@link #onIsLanguageAvailable(String, String, String)}.
    386      *
    387      * @param voiceName Name of the voice.
    388      * @return {@link TextToSpeech#ERROR} or {@link TextToSpeech#SUCCESS}.
    389      */
    390     public int onIsValidVoiceName(String voiceName) {
    391         Locale locale = Locale.forLanguageTag(voiceName);
    392         if (locale == null) {
    393             return TextToSpeech.ERROR;
    394         }
    395         int expectedStatus = getExpectedLanguageAvailableStatus(locale);
    396         try {
    397             int localeStatus = onIsLanguageAvailable(locale.getISO3Language(),
    398                     locale.getISO3Country(), locale.getVariant());
    399             if (localeStatus != expectedStatus) {
    400                 return TextToSpeech.ERROR;
    401             }
    402             return TextToSpeech.SUCCESS;
    403         } catch (MissingResourceException e) {
    404             return TextToSpeech.ERROR;
    405         }
    406     }
    407 
    408     private int getDefaultSpeechRate() {
    409         return getSecureSettingInt(Settings.Secure.TTS_DEFAULT_RATE, Engine.DEFAULT_RATE);
    410     }
    411 
    412     private int getDefaultPitch() {
    413         return getSecureSettingInt(Settings.Secure.TTS_DEFAULT_PITCH, Engine.DEFAULT_PITCH);
    414     }
    415 
    416     private String[] getSettingsLocale() {
    417         final Locale locale = mEngineHelper.getLocalePrefForEngine(mPackageName);
    418         return TtsEngines.toOldLocaleStringFormat(locale);
    419     }
    420 
    421     private int getSecureSettingInt(String name, int defaultValue) {
    422         return Settings.Secure.getInt(getContentResolver(), name, defaultValue);
    423     }
    424 
    425     /**
    426      * Synthesizer thread. This thread is used to run {@link SynthHandler}.
    427      */
    428     private class SynthThread extends HandlerThread implements MessageQueue.IdleHandler {
    429 
    430         private boolean mFirstIdle = true;
    431 
    432         public SynthThread() {
    433             super(SYNTH_THREAD_NAME, android.os.Process.THREAD_PRIORITY_DEFAULT);
    434         }
    435 
    436         @Override
    437         protected void onLooperPrepared() {
    438             getLooper().getQueue().addIdleHandler(this);
    439         }
    440 
    441         @Override
    442         public boolean queueIdle() {
    443             if (mFirstIdle) {
    444                 mFirstIdle = false;
    445             } else {
    446                 broadcastTtsQueueProcessingCompleted();
    447             }
    448             return true;
    449         }
    450 
    451         private void broadcastTtsQueueProcessingCompleted() {
    452             Intent i = new Intent(TextToSpeech.ACTION_TTS_QUEUE_PROCESSING_COMPLETED);
    453             if (DBG) Log.d(TAG, "Broadcasting: " + i);
    454             sendBroadcast(i);
    455         }
    456     }
    457 
    458     private class SynthHandler extends Handler {
    459         private SpeechItem mCurrentSpeechItem = null;
    460 
    461         // When a message with QUEUE_FLUSH arrives we add the caller identity to the List and when a
    462         // message with QUEUE_DESTROY arrives we increment mFlushAll. Then a message is added to the
    463         // handler queue that removes the caller identify from the list and decrements the mFlushAll
    464         // counter. This is so that when a message is processed and the caller identity is in the
    465         // list or mFlushAll is not zero, we know that the message should be flushed.
    466         // It's important that mFlushedObjects is a List and not a Set, and that mFlushAll is an
    467         // int and not a bool. This is because when multiple messages arrive with QUEUE_FLUSH or
    468         // QUEUE_DESTROY, we want to keep flushing messages until we arrive at the last QUEUE_FLUSH
    469         // or QUEUE_DESTROY message.
    470         private List<Object> mFlushedObjects = new ArrayList<>();
    471         private int mFlushAll = 0;
    472 
    473         public SynthHandler(Looper looper) {
    474             super(looper);
    475         }
    476 
    477         private void startFlushingSpeechItems(Object callerIdentity) {
    478             synchronized (mFlushedObjects) {
    479                 if (callerIdentity == null) {
    480                     mFlushAll += 1;
    481                 } else {
    482                     mFlushedObjects.add(callerIdentity);
    483                 }
    484             }
    485         }
    486         private void endFlushingSpeechItems(Object callerIdentity) {
    487             synchronized (mFlushedObjects) {
    488                 if (callerIdentity == null) {
    489                     mFlushAll -= 1;
    490                 } else {
    491                     mFlushedObjects.remove(callerIdentity);
    492                 }
    493             }
    494         }
    495         private boolean isFlushed(SpeechItem speechItem) {
    496             synchronized (mFlushedObjects) {
    497                 return mFlushAll > 0 || mFlushedObjects.contains(speechItem.getCallerIdentity());
    498             }
    499         }
    500 
    501         private synchronized SpeechItem getCurrentSpeechItem() {
    502             return mCurrentSpeechItem;
    503         }
    504 
    505         private synchronized boolean setCurrentSpeechItem(SpeechItem speechItem) {
    506             // Do not set as current if the item has already been flushed. The check is
    507             // intentionally put inside this synchronized method. Specifically, the following
    508             // racy sequence between this method and stopForApp() needs to be avoided.
    509             //        (this method)          (stopForApp)
    510             //     1. isFlushed
    511             //     2.                        startFlushingSpeechItems
    512             //     3.                        maybeRemoveCurrentSpeechItem
    513             //     4. set mCurrentSpeechItem
    514             // If it happens, stop() is never called on the item. The guard by synchornized(this)
    515             // ensures that the step 3 cannot interrupt between 1 and 4.
    516             if (speechItem != null && isFlushed(speechItem)) {
    517                 return false;
    518             }
    519             mCurrentSpeechItem = speechItem;
    520             return true;
    521         }
    522 
    523         private synchronized SpeechItem removeCurrentSpeechItem() {
    524             SpeechItem current = mCurrentSpeechItem;
    525             mCurrentSpeechItem = null;
    526             return current;
    527         }
    528 
    529         private synchronized SpeechItem maybeRemoveCurrentSpeechItem(Object callerIdentity) {
    530             if (mCurrentSpeechItem != null &&
    531                     (mCurrentSpeechItem.getCallerIdentity() == callerIdentity)) {
    532                 SpeechItem current = mCurrentSpeechItem;
    533                 mCurrentSpeechItem = null;
    534                 return current;
    535             }
    536 
    537             return null;
    538         }
    539 
    540         public boolean isSpeaking() {
    541             return getCurrentSpeechItem() != null;
    542         }
    543 
    544         public void quit() {
    545             // Don't process any more speech items
    546             getLooper().quit();
    547             // Stop the current speech item
    548             SpeechItem current = removeCurrentSpeechItem();
    549             if (current != null) {
    550                 current.stop();
    551             }
    552             // The AudioPlaybackHandler will be destroyed by the caller.
    553         }
    554 
    555         /**
    556          * Adds a speech item to the queue.
    557          *
    558          * Called on a service binder thread.
    559          */
    560         public int enqueueSpeechItem(int queueMode, final SpeechItem speechItem) {
    561             UtteranceProgressDispatcher utterenceProgress = null;
    562             if (speechItem instanceof UtteranceProgressDispatcher) {
    563                 utterenceProgress = (UtteranceProgressDispatcher) speechItem;
    564             }
    565 
    566             if (!speechItem.isValid()) {
    567                 if (utterenceProgress != null) {
    568                     utterenceProgress.dispatchOnError(
    569                             TextToSpeech.ERROR_INVALID_REQUEST);
    570                 }
    571                 return TextToSpeech.ERROR;
    572             }
    573 
    574             if (queueMode == TextToSpeech.QUEUE_FLUSH) {
    575                 stopForApp(speechItem.getCallerIdentity());
    576             } else if (queueMode == TextToSpeech.QUEUE_DESTROY) {
    577                 stopAll();
    578             }
    579             Runnable runnable = new Runnable() {
    580                 @Override
    581                 public void run() {
    582                     if (setCurrentSpeechItem(speechItem)) {
    583                         speechItem.play();
    584                         removeCurrentSpeechItem();
    585                     } else {
    586                         // The item is alreadly flushed. Stopping.
    587                         speechItem.stop();
    588                     }
    589                 }
    590             };
    591             Message msg = Message.obtain(this, runnable);
    592 
    593             // The obj is used to remove all callbacks from the given app in
    594             // stopForApp(String).
    595             //
    596             // Note that this string is interned, so the == comparison works.
    597             msg.obj = speechItem.getCallerIdentity();
    598 
    599             if (sendMessage(msg)) {
    600                 return TextToSpeech.SUCCESS;
    601             } else {
    602                 Log.w(TAG, "SynthThread has quit");
    603                 if (utterenceProgress != null) {
    604                     utterenceProgress.dispatchOnError(TextToSpeech.ERROR_SERVICE);
    605                 }
    606                 return TextToSpeech.ERROR;
    607             }
    608         }
    609 
    610         /**
    611          * Stops all speech output and removes any utterances still in the queue for
    612          * the calling app.
    613          *
    614          * Called on a service binder thread.
    615          */
    616         public int stopForApp(final Object callerIdentity) {
    617             if (callerIdentity == null) {
    618                 return TextToSpeech.ERROR;
    619             }
    620 
    621             // Flush pending messages from callerIdentity.
    622             // See setCurrentSpeechItem on a subtlety around a race condition.
    623             startFlushingSpeechItems(callerIdentity);
    624 
    625             // This stops writing data to the file / or publishing
    626             // items to the audio playback handler.
    627             //
    628             // Note that the current speech item must be removed only if it
    629             // belongs to the callingApp, else the item will be "orphaned" and
    630             // not stopped correctly if a stop request comes along for the item
    631             // from the app it belongs to.
    632             SpeechItem current = maybeRemoveCurrentSpeechItem(callerIdentity);
    633             if (current != null) {
    634                 current.stop();
    635             }
    636 
    637             // Remove any enqueued audio too.
    638             mAudioPlaybackHandler.stopForApp(callerIdentity);
    639 
    640             // Stop flushing pending messages
    641             Runnable runnable = new Runnable() {
    642                 @Override
    643                 public void run() {
    644                     endFlushingSpeechItems(callerIdentity);
    645                 }
    646             };
    647             sendMessage(Message.obtain(this, runnable));
    648             return TextToSpeech.SUCCESS;
    649         }
    650 
    651         public int stopAll() {
    652             // Order to flush pending messages
    653             startFlushingSpeechItems(null);
    654 
    655             // Stop the current speech item unconditionally .
    656             SpeechItem current = removeCurrentSpeechItem();
    657             if (current != null) {
    658                 current.stop();
    659             }
    660             // Remove all pending playback as well.
    661             mAudioPlaybackHandler.stop();
    662 
    663             // Message to stop flushing pending messages
    664             Runnable runnable = new Runnable() {
    665                 @Override
    666                 public void run() {
    667                     endFlushingSpeechItems(null);
    668                 }
    669             };
    670             sendMessage(Message.obtain(this, runnable));
    671 
    672 
    673             return TextToSpeech.SUCCESS;
    674         }
    675     }
    676 
    677     interface UtteranceProgressDispatcher {
    678         void dispatchOnStop();
    679 
    680         void dispatchOnSuccess();
    681 
    682         void dispatchOnStart();
    683 
    684         void dispatchOnError(int errorCode);
    685 
    686         void dispatchOnBeginSynthesis(int sampleRateInHz, int audioFormat, int channelCount);
    687 
    688         void dispatchOnAudioAvailable(byte[] audio);
    689 
    690         public void dispatchOnRangeStart(int start, int end, int frame);
    691     }
    692 
    693     /** Set of parameters affecting audio output. */
    694     static class AudioOutputParams {
    695         /**
    696          * Audio session identifier. May be used to associate audio playback with one of the
    697          * {@link android.media.audiofx.AudioEffect} objects. If not specified by client,
    698          * it should be equal to {@link AudioManager#AUDIO_SESSION_ID_GENERATE}.
    699          */
    700         public final int mSessionId;
    701 
    702         /**
    703          * Volume, in the range [0.0f, 1.0f]. The default value is
    704          * {@link TextToSpeech.Engine#DEFAULT_VOLUME} (1.0f).
    705          */
    706         public final float mVolume;
    707 
    708         /**
    709          * Left/right position of the audio, in the range [-1.0f, 1.0f].
    710          * The default value is {@link TextToSpeech.Engine#DEFAULT_PAN} (0.0f).
    711          */
    712         public final float mPan;
    713 
    714 
    715         /**
    716          * Audio attributes, set by {@link TextToSpeech#setAudioAttributes}
    717          * or created from the value of {@link TextToSpeech.Engine#KEY_PARAM_STREAM}.
    718          */
    719         public final AudioAttributes mAudioAttributes;
    720 
    721         /** Create AudioOutputParams with default values */
    722         AudioOutputParams() {
    723             mSessionId = AudioManager.AUDIO_SESSION_ID_GENERATE;
    724             mVolume = Engine.DEFAULT_VOLUME;
    725             mPan = Engine.DEFAULT_PAN;
    726             mAudioAttributes = null;
    727         }
    728 
    729         AudioOutputParams(int sessionId, float volume, float pan,
    730                 AudioAttributes audioAttributes) {
    731             mSessionId = sessionId;
    732             mVolume = volume;
    733             mPan = pan;
    734             mAudioAttributes = audioAttributes;
    735         }
    736 
    737         /** Create AudioOutputParams from A {@link SynthesisRequest#getParams()} bundle */
    738         static AudioOutputParams createFromParamsBundle(Bundle paramsBundle, boolean isSpeech) {
    739             if (paramsBundle == null) {
    740                 return new AudioOutputParams();
    741             }
    742 
    743             AudioAttributes audioAttributes =
    744                     (AudioAttributes) paramsBundle.getParcelable(
    745                             Engine.KEY_PARAM_AUDIO_ATTRIBUTES);
    746             if (audioAttributes == null) {
    747                 int streamType = paramsBundle.getInt(
    748                         Engine.KEY_PARAM_STREAM, Engine.DEFAULT_STREAM);
    749                 audioAttributes = (new AudioAttributes.Builder())
    750                         .setLegacyStreamType(streamType)
    751                         .setContentType((isSpeech ?
    752                                 AudioAttributes.CONTENT_TYPE_SPEECH :
    753                                 AudioAttributes.CONTENT_TYPE_SONIFICATION))
    754                         .build();
    755             }
    756 
    757             return new AudioOutputParams(
    758                     paramsBundle.getInt(
    759                             Engine.KEY_PARAM_SESSION_ID,
    760                             AudioManager.AUDIO_SESSION_ID_GENERATE),
    761                     paramsBundle.getFloat(
    762                             Engine.KEY_PARAM_VOLUME,
    763                             Engine.DEFAULT_VOLUME),
    764                     paramsBundle.getFloat(
    765                             Engine.KEY_PARAM_PAN,
    766                             Engine.DEFAULT_PAN),
    767                     audioAttributes);
    768         }
    769     }
    770 
    771 
    772     /**
    773      * An item in the synth thread queue.
    774      */
    775     private abstract class SpeechItem {
    776         private final Object mCallerIdentity;
    777         private final int mCallerUid;
    778         private final int mCallerPid;
    779         private boolean mStarted = false;
    780         private boolean mStopped = false;
    781 
    782         public SpeechItem(Object caller, int callerUid, int callerPid) {
    783             mCallerIdentity = caller;
    784             mCallerUid = callerUid;
    785             mCallerPid = callerPid;
    786         }
    787 
    788         public Object getCallerIdentity() {
    789             return mCallerIdentity;
    790         }
    791 
    792         public int getCallerUid() {
    793             return mCallerUid;
    794         }
    795 
    796         public int getCallerPid() {
    797             return mCallerPid;
    798         }
    799 
    800         /**
    801          * Checker whether the item is valid. If this method returns false, the item should not
    802          * be played.
    803          */
    804         public abstract boolean isValid();
    805 
    806         /**
    807          * Plays the speech item. Blocks until playback is finished.
    808          * Must not be called more than once.
    809          *
    810          * Only called on the synthesis thread.
    811          */
    812         public void play() {
    813             synchronized (this) {
    814                 if (mStarted) {
    815                     throw new IllegalStateException("play() called twice");
    816                 }
    817                 mStarted = true;
    818             }
    819             playImpl();
    820         }
    821 
    822         protected abstract void playImpl();
    823 
    824         /**
    825          * Stops the speech item.
    826          * Must not be called more than once.
    827          *
    828          * Can be called on multiple threads,  but not on the synthesis thread.
    829          */
    830         public void stop() {
    831             synchronized (this) {
    832                 if (mStopped) {
    833                     throw new IllegalStateException("stop() called twice");
    834                 }
    835                 mStopped = true;
    836             }
    837             stopImpl();
    838         }
    839 
    840         protected abstract void stopImpl();
    841 
    842         protected synchronized boolean isStopped() {
    843              return mStopped;
    844         }
    845 
    846         protected synchronized boolean isStarted() {
    847             return mStarted;
    848        }
    849     }
    850 
    851     /**
    852      * An item in the synth thread queue that process utterance (and call back to client about
    853      * progress).
    854      */
    855     private abstract class UtteranceSpeechItem extends SpeechItem
    856         implements UtteranceProgressDispatcher  {
    857 
    858         public UtteranceSpeechItem(Object caller, int callerUid, int callerPid) {
    859             super(caller, callerUid, callerPid);
    860         }
    861 
    862         @Override
    863         public void dispatchOnSuccess() {
    864             final String utteranceId = getUtteranceId();
    865             if (utteranceId != null) {
    866                 mCallbacks.dispatchOnSuccess(getCallerIdentity(), utteranceId);
    867             }
    868         }
    869 
    870         @Override
    871         public void dispatchOnStop() {
    872             final String utteranceId = getUtteranceId();
    873             if (utteranceId != null) {
    874                 mCallbacks.dispatchOnStop(getCallerIdentity(), utteranceId, isStarted());
    875             }
    876         }
    877 
    878         @Override
    879         public void dispatchOnStart() {
    880             final String utteranceId = getUtteranceId();
    881             if (utteranceId != null) {
    882                 mCallbacks.dispatchOnStart(getCallerIdentity(), utteranceId);
    883             }
    884         }
    885 
    886         @Override
    887         public void dispatchOnError(int errorCode) {
    888             final String utteranceId = getUtteranceId();
    889             if (utteranceId != null) {
    890                 mCallbacks.dispatchOnError(getCallerIdentity(), utteranceId, errorCode);
    891             }
    892         }
    893 
    894         @Override
    895         public void dispatchOnBeginSynthesis(int sampleRateInHz, int audioFormat, int channelCount) {
    896             final String utteranceId = getUtteranceId();
    897             if (utteranceId != null) {
    898                 mCallbacks.dispatchOnBeginSynthesis(getCallerIdentity(), utteranceId, sampleRateInHz, audioFormat, channelCount);
    899             }
    900         }
    901 
    902         @Override
    903         public void dispatchOnAudioAvailable(byte[] audio) {
    904             final String utteranceId = getUtteranceId();
    905             if (utteranceId != null) {
    906                 mCallbacks.dispatchOnAudioAvailable(getCallerIdentity(), utteranceId, audio);
    907             }
    908         }
    909 
    910         @Override
    911         public void dispatchOnRangeStart(int start, int end, int frame) {
    912             final String utteranceId = getUtteranceId();
    913             if (utteranceId != null) {
    914                 mCallbacks.dispatchOnRangeStart(
    915                         getCallerIdentity(), utteranceId, start, end, frame);
    916             }
    917         }
    918 
    919         abstract public String getUtteranceId();
    920 
    921         String getStringParam(Bundle params, String key, String defaultValue) {
    922             return params == null ? defaultValue : params.getString(key, defaultValue);
    923         }
    924 
    925         int getIntParam(Bundle params, String key, int defaultValue) {
    926             return params == null ? defaultValue : params.getInt(key, defaultValue);
    927         }
    928 
    929         float getFloatParam(Bundle params, String key, float defaultValue) {
    930             return params == null ? defaultValue : params.getFloat(key, defaultValue);
    931         }
    932     }
    933 
    934     /**
    935      * Synthesis parameters are kept in a single Bundle passed as parameter. This class allow
    936      * subclasses to access them conveniently.
    937      */
    938     private abstract class UtteranceSpeechItemWithParams extends UtteranceSpeechItem {
    939         protected final Bundle mParams;
    940         protected final String mUtteranceId;
    941 
    942         UtteranceSpeechItemWithParams(
    943                 Object callerIdentity,
    944                 int callerUid,
    945                 int callerPid,
    946                 Bundle params,
    947                 String utteranceId) {
    948             super(callerIdentity, callerUid, callerPid);
    949             mParams = params;
    950             mUtteranceId = utteranceId;
    951         }
    952 
    953         boolean hasLanguage() {
    954             return !TextUtils.isEmpty(getStringParam(mParams, Engine.KEY_PARAM_LANGUAGE, null));
    955         }
    956 
    957         int getSpeechRate() {
    958             return getIntParam(mParams, Engine.KEY_PARAM_RATE, getDefaultSpeechRate());
    959         }
    960 
    961         int getPitch() {
    962             return getIntParam(mParams, Engine.KEY_PARAM_PITCH, getDefaultPitch());
    963         }
    964 
    965         @Override
    966         public String getUtteranceId() {
    967             return mUtteranceId;
    968         }
    969 
    970         AudioOutputParams getAudioParams() {
    971             return AudioOutputParams.createFromParamsBundle(mParams, true);
    972         }
    973     }
    974 
    975     class SynthesisSpeechItem extends UtteranceSpeechItemWithParams {
    976         // Never null.
    977         private final CharSequence mText;
    978         private final SynthesisRequest mSynthesisRequest;
    979         private final String[] mDefaultLocale;
    980         // Non null after synthesis has started, and all accesses
    981         // guarded by 'this'.
    982         private AbstractSynthesisCallback mSynthesisCallback;
    983         private final EventLogger mEventLogger;
    984         private final int mCallerUid;
    985 
    986         public SynthesisSpeechItem(
    987                 Object callerIdentity,
    988                 int callerUid,
    989                 int callerPid,
    990                 Bundle params,
    991                 String utteranceId,
    992                 CharSequence text) {
    993             super(callerIdentity, callerUid, callerPid, params, utteranceId);
    994             mText = text;
    995             mCallerUid = callerUid;
    996             mSynthesisRequest = new SynthesisRequest(mText, mParams);
    997             mDefaultLocale = getSettingsLocale();
    998             setRequestParams(mSynthesisRequest);
    999             mEventLogger = new EventLogger(mSynthesisRequest, callerUid, callerPid, mPackageName);
   1000         }
   1001 
   1002         public CharSequence getText() {
   1003             return mText;
   1004         }
   1005 
   1006         @Override
   1007         public boolean isValid() {
   1008             if (mText == null) {
   1009                 Log.e(TAG, "null synthesis text");
   1010                 return false;
   1011             }
   1012             if (mText.length() >= TextToSpeech.getMaxSpeechInputLength()) {
   1013                 Log.w(TAG, "Text too long: " + mText.length() + " chars");
   1014                 return false;
   1015             }
   1016             return true;
   1017         }
   1018 
   1019         @Override
   1020         protected void playImpl() {
   1021             AbstractSynthesisCallback synthesisCallback;
   1022             mEventLogger.onRequestProcessingStart();
   1023             synchronized (this) {
   1024                 // stop() might have been called before we enter this
   1025                 // synchronized block.
   1026                 if (isStopped()) {
   1027                     return;
   1028                 }
   1029                 mSynthesisCallback = createSynthesisCallback();
   1030                 synthesisCallback = mSynthesisCallback;
   1031             }
   1032 
   1033             TextToSpeechService.this.onSynthesizeText(mSynthesisRequest, synthesisCallback);
   1034 
   1035             // Fix for case where client called .start() & .error(), but did not called .done()
   1036             if (synthesisCallback.hasStarted() && !synthesisCallback.hasFinished()) {
   1037                 synthesisCallback.done();
   1038             }
   1039         }
   1040 
   1041         protected AbstractSynthesisCallback createSynthesisCallback() {
   1042             return new PlaybackSynthesisCallback(getAudioParams(),
   1043                     mAudioPlaybackHandler, this, getCallerIdentity(), mEventLogger, false);
   1044         }
   1045 
   1046         private void setRequestParams(SynthesisRequest request) {
   1047             String voiceName = getVoiceName();
   1048             request.setLanguage(getLanguage(), getCountry(), getVariant());
   1049             if (!TextUtils.isEmpty(voiceName)) {
   1050                 request.setVoiceName(getVoiceName());
   1051             }
   1052             request.setSpeechRate(getSpeechRate());
   1053             request.setCallerUid(mCallerUid);
   1054             request.setPitch(getPitch());
   1055         }
   1056 
   1057         @Override
   1058         protected void stopImpl() {
   1059             AbstractSynthesisCallback synthesisCallback;
   1060             synchronized (this) {
   1061                 synthesisCallback = mSynthesisCallback;
   1062             }
   1063             if (synthesisCallback != null) {
   1064                 // If the synthesis callback is null, it implies that we haven't
   1065                 // entered the synchronized(this) block in playImpl which in
   1066                 // turn implies that synthesis would not have started.
   1067                 synthesisCallback.stop();
   1068                 TextToSpeechService.this.onStop();
   1069             } else {
   1070                 dispatchOnStop();
   1071             }
   1072         }
   1073 
   1074         private String getCountry() {
   1075             if (!hasLanguage()) return mDefaultLocale[1];
   1076             return getStringParam(mParams, Engine.KEY_PARAM_COUNTRY, "");
   1077         }
   1078 
   1079         private String getVariant() {
   1080             if (!hasLanguage()) return mDefaultLocale[2];
   1081             return getStringParam(mParams, Engine.KEY_PARAM_VARIANT, "");
   1082         }
   1083 
   1084         public String getLanguage() {
   1085             return getStringParam(mParams, Engine.KEY_PARAM_LANGUAGE, mDefaultLocale[0]);
   1086         }
   1087 
   1088         public String getVoiceName() {
   1089             return getStringParam(mParams, Engine.KEY_PARAM_VOICE_NAME, "");
   1090         }
   1091     }
   1092 
   1093     private class SynthesisToFileOutputStreamSpeechItem extends SynthesisSpeechItem {
   1094         private final FileOutputStream mFileOutputStream;
   1095 
   1096         public SynthesisToFileOutputStreamSpeechItem(
   1097                 Object callerIdentity,
   1098                 int callerUid,
   1099                 int callerPid,
   1100                 Bundle params,
   1101                 String utteranceId,
   1102                 CharSequence text,
   1103                 FileOutputStream fileOutputStream) {
   1104             super(callerIdentity, callerUid, callerPid, params, utteranceId, text);
   1105             mFileOutputStream = fileOutputStream;
   1106         }
   1107 
   1108         @Override
   1109         protected AbstractSynthesisCallback createSynthesisCallback() {
   1110             return new FileSynthesisCallback(mFileOutputStream.getChannel(), this, false);
   1111         }
   1112 
   1113         @Override
   1114         protected void playImpl() {
   1115             dispatchOnStart();
   1116             super.playImpl();
   1117             try {
   1118               mFileOutputStream.close();
   1119             } catch(IOException e) {
   1120               Log.w(TAG, "Failed to close output file", e);
   1121             }
   1122         }
   1123     }
   1124 
   1125     private class AudioSpeechItem extends UtteranceSpeechItemWithParams {
   1126         private final AudioPlaybackQueueItem mItem;
   1127 
   1128         public AudioSpeechItem(
   1129                 Object callerIdentity,
   1130                 int callerUid,
   1131                 int callerPid,
   1132                 Bundle params,
   1133                 String utteranceId,
   1134                 Uri uri) {
   1135             super(callerIdentity, callerUid, callerPid, params, utteranceId);
   1136             mItem = new AudioPlaybackQueueItem(this, getCallerIdentity(),
   1137                     TextToSpeechService.this, uri, getAudioParams());
   1138         }
   1139 
   1140         @Override
   1141         public boolean isValid() {
   1142             return true;
   1143         }
   1144 
   1145         @Override
   1146         protected void playImpl() {
   1147             mAudioPlaybackHandler.enqueue(mItem);
   1148         }
   1149 
   1150         @Override
   1151         protected void stopImpl() {
   1152             // Do nothing.
   1153         }
   1154 
   1155         @Override
   1156         public String getUtteranceId() {
   1157             return getStringParam(mParams, Engine.KEY_PARAM_UTTERANCE_ID, null);
   1158         }
   1159 
   1160         @Override
   1161         AudioOutputParams getAudioParams() {
   1162             return AudioOutputParams.createFromParamsBundle(mParams, false);
   1163         }
   1164     }
   1165 
   1166     private class SilenceSpeechItem extends UtteranceSpeechItem {
   1167         private final long mDuration;
   1168         private final String mUtteranceId;
   1169 
   1170         public SilenceSpeechItem(Object callerIdentity, int callerUid, int callerPid,
   1171                 String utteranceId, long duration) {
   1172             super(callerIdentity, callerUid, callerPid);
   1173             mUtteranceId = utteranceId;
   1174             mDuration = duration;
   1175         }
   1176 
   1177         @Override
   1178         public boolean isValid() {
   1179             return true;
   1180         }
   1181 
   1182         @Override
   1183         protected void playImpl() {
   1184             mAudioPlaybackHandler.enqueue(new SilencePlaybackQueueItem(
   1185                     this, getCallerIdentity(), mDuration));
   1186         }
   1187 
   1188         @Override
   1189         protected void stopImpl() {
   1190 
   1191         }
   1192 
   1193         @Override
   1194         public String getUtteranceId() {
   1195             return mUtteranceId;
   1196         }
   1197     }
   1198 
   1199     /**
   1200      * Call {@link TextToSpeechService#onLoadLanguage} on synth thread.
   1201      */
   1202     private class LoadLanguageItem extends SpeechItem {
   1203         private final String mLanguage;
   1204         private final String mCountry;
   1205         private final String mVariant;
   1206 
   1207         public LoadLanguageItem(Object callerIdentity, int callerUid, int callerPid,
   1208                 String language, String country, String variant) {
   1209             super(callerIdentity, callerUid, callerPid);
   1210             mLanguage = language;
   1211             mCountry = country;
   1212             mVariant = variant;
   1213         }
   1214 
   1215         @Override
   1216         public boolean isValid() {
   1217             return true;
   1218         }
   1219 
   1220         @Override
   1221         protected void playImpl() {
   1222             TextToSpeechService.this.onLoadLanguage(mLanguage, mCountry, mVariant);
   1223         }
   1224 
   1225         @Override
   1226         protected void stopImpl() {
   1227             // No-op
   1228         }
   1229     }
   1230 
   1231     /**
   1232      * Call {@link TextToSpeechService#onLoadLanguage} on synth thread.
   1233      */
   1234     private class LoadVoiceItem extends SpeechItem {
   1235         private final String mVoiceName;
   1236 
   1237         public LoadVoiceItem(Object callerIdentity, int callerUid, int callerPid,
   1238                 String voiceName) {
   1239             super(callerIdentity, callerUid, callerPid);
   1240             mVoiceName = voiceName;
   1241         }
   1242 
   1243         @Override
   1244         public boolean isValid() {
   1245             return true;
   1246         }
   1247 
   1248         @Override
   1249         protected void playImpl() {
   1250             TextToSpeechService.this.onLoadVoice(mVoiceName);
   1251         }
   1252 
   1253         @Override
   1254         protected void stopImpl() {
   1255             // No-op
   1256         }
   1257     }
   1258 
   1259 
   1260     @Override
   1261     public IBinder onBind(Intent intent) {
   1262         if (TextToSpeech.Engine.INTENT_ACTION_TTS_SERVICE.equals(intent.getAction())) {
   1263             return mBinder;
   1264         }
   1265         return null;
   1266     }
   1267 
   1268     /**
   1269      * Binder returned from {@code #onBind(Intent)}. The methods in this class can be called called
   1270      * from several different threads.
   1271      */
   1272     // NOTE: All calls that are passed in a calling app are interned so that
   1273     // they can be used as message objects (which are tested for equality using ==).
   1274     private final ITextToSpeechService.Stub mBinder =
   1275             new ITextToSpeechService.Stub() {
   1276                 @Override
   1277                 public int speak(
   1278                         IBinder caller,
   1279                         CharSequence text,
   1280                         int queueMode,
   1281                         Bundle params,
   1282                         String utteranceId) {
   1283                     if (!checkNonNull(caller, text, params)) {
   1284                         return TextToSpeech.ERROR;
   1285                     }
   1286 
   1287                     SpeechItem item =
   1288                             new SynthesisSpeechItem(
   1289                                     caller,
   1290                                     Binder.getCallingUid(),
   1291                                     Binder.getCallingPid(),
   1292                                     params,
   1293                                     utteranceId,
   1294                                     text);
   1295                     return mSynthHandler.enqueueSpeechItem(queueMode, item);
   1296                 }
   1297 
   1298                 @Override
   1299                 public int synthesizeToFileDescriptor(
   1300                         IBinder caller,
   1301                         CharSequence text,
   1302                         ParcelFileDescriptor fileDescriptor,
   1303                         Bundle params,
   1304                         String utteranceId) {
   1305                     if (!checkNonNull(caller, text, fileDescriptor, params)) {
   1306                         return TextToSpeech.ERROR;
   1307                     }
   1308 
   1309                     // In test env, ParcelFileDescriptor instance may be EXACTLY the same
   1310                     // one that is used by client. And it will be closed by a client, thus
   1311                     // preventing us from writing anything to it.
   1312                     final ParcelFileDescriptor sameFileDescriptor =
   1313                             ParcelFileDescriptor.adoptFd(fileDescriptor.detachFd());
   1314 
   1315                     SpeechItem item =
   1316                             new SynthesisToFileOutputStreamSpeechItem(
   1317                                     caller,
   1318                                     Binder.getCallingUid(),
   1319                                     Binder.getCallingPid(),
   1320                                     params,
   1321                                     utteranceId,
   1322                                     text,
   1323                                     new ParcelFileDescriptor.AutoCloseOutputStream(
   1324                                             sameFileDescriptor));
   1325                     return mSynthHandler.enqueueSpeechItem(TextToSpeech.QUEUE_ADD, item);
   1326                 }
   1327 
   1328                 @Override
   1329                 public int playAudio(
   1330                         IBinder caller,
   1331                         Uri audioUri,
   1332                         int queueMode,
   1333                         Bundle params,
   1334                         String utteranceId) {
   1335                     if (!checkNonNull(caller, audioUri, params)) {
   1336                         return TextToSpeech.ERROR;
   1337                     }
   1338 
   1339                     SpeechItem item =
   1340                             new AudioSpeechItem(
   1341                                     caller,
   1342                                     Binder.getCallingUid(),
   1343                                     Binder.getCallingPid(),
   1344                                     params,
   1345                                     utteranceId,
   1346                                     audioUri);
   1347                     return mSynthHandler.enqueueSpeechItem(queueMode, item);
   1348                 }
   1349 
   1350                 @Override
   1351                 public int playSilence(
   1352                         IBinder caller, long duration, int queueMode, String utteranceId) {
   1353                     if (!checkNonNull(caller)) {
   1354                         return TextToSpeech.ERROR;
   1355                     }
   1356 
   1357                     SpeechItem item =
   1358                             new SilenceSpeechItem(
   1359                                     caller,
   1360                                     Binder.getCallingUid(),
   1361                                     Binder.getCallingPid(),
   1362                                     utteranceId,
   1363                                     duration);
   1364                     return mSynthHandler.enqueueSpeechItem(queueMode, item);
   1365                 }
   1366 
   1367                 @Override
   1368                 public boolean isSpeaking() {
   1369                     return mSynthHandler.isSpeaking() || mAudioPlaybackHandler.isSpeaking();
   1370                 }
   1371 
   1372                 @Override
   1373                 public int stop(IBinder caller) {
   1374                     if (!checkNonNull(caller)) {
   1375                         return TextToSpeech.ERROR;
   1376                     }
   1377 
   1378                     return mSynthHandler.stopForApp(caller);
   1379                 }
   1380 
   1381                 @Override
   1382                 public String[] getLanguage() {
   1383                     return onGetLanguage();
   1384                 }
   1385 
   1386                 @Override
   1387                 public String[] getClientDefaultLanguage() {
   1388                     return getSettingsLocale();
   1389                 }
   1390 
   1391                 /*
   1392                  * If defaults are enforced, then no language is "available" except
   1393                  * perhaps the default language selected by the user.
   1394                  */
   1395                 @Override
   1396                 public int isLanguageAvailable(String lang, String country, String variant) {
   1397                     if (!checkNonNull(lang)) {
   1398                         return TextToSpeech.ERROR;
   1399                     }
   1400 
   1401                     return onIsLanguageAvailable(lang, country, variant);
   1402                 }
   1403 
   1404                 @Override
   1405                 public String[] getFeaturesForLanguage(
   1406                         String lang, String country, String variant) {
   1407                     Set<String> features = onGetFeaturesForLanguage(lang, country, variant);
   1408                     String[] featuresArray = null;
   1409                     if (features != null) {
   1410                         featuresArray = new String[features.size()];
   1411                         features.toArray(featuresArray);
   1412                     } else {
   1413                         featuresArray = new String[0];
   1414                     }
   1415                     return featuresArray;
   1416                 }
   1417 
   1418                 /*
   1419                  * There is no point loading a non default language if defaults
   1420                  * are enforced.
   1421                  */
   1422                 @Override
   1423                 public int loadLanguage(
   1424                         IBinder caller, String lang, String country, String variant) {
   1425                     if (!checkNonNull(lang)) {
   1426                         return TextToSpeech.ERROR;
   1427                     }
   1428                     int retVal = onIsLanguageAvailable(lang, country, variant);
   1429 
   1430                     if (retVal == TextToSpeech.LANG_AVAILABLE
   1431                             || retVal == TextToSpeech.LANG_COUNTRY_AVAILABLE
   1432                             || retVal == TextToSpeech.LANG_COUNTRY_VAR_AVAILABLE) {
   1433 
   1434                         SpeechItem item =
   1435                                 new LoadLanguageItem(
   1436                                         caller,
   1437                                         Binder.getCallingUid(),
   1438                                         Binder.getCallingPid(),
   1439                                         lang,
   1440                                         country,
   1441                                         variant);
   1442 
   1443                         if (mSynthHandler.enqueueSpeechItem(TextToSpeech.QUEUE_ADD, item)
   1444                                 != TextToSpeech.SUCCESS) {
   1445                             return TextToSpeech.ERROR;
   1446                         }
   1447                     }
   1448                     return retVal;
   1449                 }
   1450 
   1451                 @Override
   1452                 public List<Voice> getVoices() {
   1453                     return onGetVoices();
   1454                 }
   1455 
   1456                 @Override
   1457                 public int loadVoice(IBinder caller, String voiceName) {
   1458                     if (!checkNonNull(voiceName)) {
   1459                         return TextToSpeech.ERROR;
   1460                     }
   1461                     int retVal = onIsValidVoiceName(voiceName);
   1462 
   1463                     if (retVal == TextToSpeech.SUCCESS) {
   1464                         SpeechItem item =
   1465                                 new LoadVoiceItem(
   1466                                         caller,
   1467                                         Binder.getCallingUid(),
   1468                                         Binder.getCallingPid(),
   1469                                         voiceName);
   1470                         if (mSynthHandler.enqueueSpeechItem(TextToSpeech.QUEUE_ADD, item)
   1471                                 != TextToSpeech.SUCCESS) {
   1472                             return TextToSpeech.ERROR;
   1473                         }
   1474                     }
   1475                     return retVal;
   1476                 }
   1477 
   1478                 public String getDefaultVoiceNameFor(String lang, String country, String variant) {
   1479                     if (!checkNonNull(lang)) {
   1480                         return null;
   1481                     }
   1482                     int retVal = onIsLanguageAvailable(lang, country, variant);
   1483 
   1484                     if (retVal == TextToSpeech.LANG_AVAILABLE
   1485                             || retVal == TextToSpeech.LANG_COUNTRY_AVAILABLE
   1486                             || retVal == TextToSpeech.LANG_COUNTRY_VAR_AVAILABLE) {
   1487                         return onGetDefaultVoiceNameFor(lang, country, variant);
   1488                     } else {
   1489                         return null;
   1490                     }
   1491                 }
   1492 
   1493                 @Override
   1494                 public void setCallback(IBinder caller, ITextToSpeechCallback cb) {
   1495                     // Note that passing in a null callback is a valid use case.
   1496                     if (!checkNonNull(caller)) {
   1497                         return;
   1498                     }
   1499 
   1500                     mCallbacks.setCallback(caller, cb);
   1501                 }
   1502 
   1503                 private String intern(String in) {
   1504                     // The input parameter will be non null.
   1505                     return in.intern();
   1506                 }
   1507 
   1508                 private boolean checkNonNull(Object... args) {
   1509                     for (Object o : args) {
   1510                         if (o == null) return false;
   1511                     }
   1512                     return true;
   1513                 }
   1514             };
   1515 
   1516     private class CallbackMap extends RemoteCallbackList<ITextToSpeechCallback> {
   1517         private final HashMap<IBinder, ITextToSpeechCallback> mCallerToCallback
   1518                 = new HashMap<IBinder, ITextToSpeechCallback>();
   1519 
   1520         public void setCallback(IBinder caller, ITextToSpeechCallback cb) {
   1521             synchronized (mCallerToCallback) {
   1522                 ITextToSpeechCallback old;
   1523                 if (cb != null) {
   1524                     register(cb, caller);
   1525                     old = mCallerToCallback.put(caller, cb);
   1526                 } else {
   1527                     old = mCallerToCallback.remove(caller);
   1528                 }
   1529                 if (old != null && old != cb) {
   1530                     unregister(old);
   1531                 }
   1532             }
   1533         }
   1534 
   1535         public void dispatchOnStop(Object callerIdentity, String utteranceId, boolean started) {
   1536             ITextToSpeechCallback cb = getCallbackFor(callerIdentity);
   1537             if (cb == null) return;
   1538             try {
   1539                 cb.onStop(utteranceId, started);
   1540             } catch (RemoteException e) {
   1541                 Log.e(TAG, "Callback onStop failed: " + e);
   1542             }
   1543         }
   1544 
   1545         public void dispatchOnSuccess(Object callerIdentity, String utteranceId) {
   1546             ITextToSpeechCallback cb = getCallbackFor(callerIdentity);
   1547             if (cb == null) return;
   1548             try {
   1549                 cb.onSuccess(utteranceId);
   1550             } catch (RemoteException e) {
   1551                 Log.e(TAG, "Callback onDone failed: " + e);
   1552             }
   1553         }
   1554 
   1555         public void dispatchOnStart(Object callerIdentity, String utteranceId) {
   1556             ITextToSpeechCallback cb = getCallbackFor(callerIdentity);
   1557             if (cb == null) return;
   1558             try {
   1559                 cb.onStart(utteranceId);
   1560             } catch (RemoteException e) {
   1561                 Log.e(TAG, "Callback onStart failed: " + e);
   1562             }
   1563         }
   1564 
   1565         public void dispatchOnError(Object callerIdentity, String utteranceId,
   1566                 int errorCode) {
   1567             ITextToSpeechCallback cb = getCallbackFor(callerIdentity);
   1568             if (cb == null) return;
   1569             try {
   1570                 cb.onError(utteranceId, errorCode);
   1571             } catch (RemoteException e) {
   1572                 Log.e(TAG, "Callback onError failed: " + e);
   1573             }
   1574         }
   1575 
   1576         public void dispatchOnBeginSynthesis(Object callerIdentity, String utteranceId, int sampleRateInHz, int audioFormat, int channelCount) {
   1577             ITextToSpeechCallback cb = getCallbackFor(callerIdentity);
   1578             if (cb == null) return;
   1579             try {
   1580                 cb.onBeginSynthesis(utteranceId, sampleRateInHz, audioFormat, channelCount);
   1581             } catch (RemoteException e) {
   1582                 Log.e(TAG, "Callback dispatchOnBeginSynthesis(String, int, int, int) failed: " + e);
   1583             }
   1584         }
   1585 
   1586         public void dispatchOnAudioAvailable(Object callerIdentity, String utteranceId, byte[] buffer) {
   1587             ITextToSpeechCallback cb = getCallbackFor(callerIdentity);
   1588             if (cb == null) return;
   1589             try {
   1590                 cb.onAudioAvailable(utteranceId, buffer);
   1591             } catch (RemoteException e) {
   1592                 Log.e(TAG, "Callback dispatchOnAudioAvailable(String, byte[]) failed: " + e);
   1593             }
   1594         }
   1595 
   1596         public void dispatchOnRangeStart(
   1597                 Object callerIdentity, String utteranceId, int start, int end, int frame) {
   1598             ITextToSpeechCallback cb = getCallbackFor(callerIdentity);
   1599             if (cb == null) return;
   1600             try {
   1601                 cb.onRangeStart(utteranceId, start, end, frame);
   1602             } catch (RemoteException e) {
   1603                 Log.e(TAG, "Callback dispatchOnRangeStart(String, int, int, int) failed: " + e);
   1604             }
   1605         }
   1606 
   1607         @Override
   1608         public void onCallbackDied(ITextToSpeechCallback callback, Object cookie) {
   1609             IBinder caller = (IBinder) cookie;
   1610             synchronized (mCallerToCallback) {
   1611                 mCallerToCallback.remove(caller);
   1612             }
   1613             //mSynthHandler.stopForApp(caller);
   1614         }
   1615 
   1616         @Override
   1617         public void kill() {
   1618             synchronized (mCallerToCallback) {
   1619                 mCallerToCallback.clear();
   1620                 super.kill();
   1621             }
   1622         }
   1623 
   1624         private ITextToSpeechCallback getCallbackFor(Object caller) {
   1625             ITextToSpeechCallback cb;
   1626             IBinder asBinder = (IBinder) caller;
   1627             synchronized (mCallerToCallback) {
   1628                 cb = mCallerToCallback.get(asBinder);
   1629             }
   1630 
   1631             return cb;
   1632         }
   1633     }
   1634 }
   1635