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