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.net.Uri;
     21 import android.os.Binder;
     22 import android.os.Bundle;
     23 import android.os.Handler;
     24 import android.os.HandlerThread;
     25 import android.os.IBinder;
     26 import android.os.Looper;
     27 import android.os.Message;
     28 import android.os.MessageQueue;
     29 import android.os.RemoteCallbackList;
     30 import android.os.RemoteException;
     31 import android.provider.Settings;
     32 import android.speech.tts.TextToSpeech.Engine;
     33 import android.text.TextUtils;
     34 import android.util.Log;
     35 
     36 import java.io.File;
     37 import java.io.IOException;
     38 import java.util.HashMap;
     39 import java.util.Locale;
     40 import java.util.Set;
     41 
     42 
     43 /**
     44  * Abstract base class for TTS engine implementations. The following methods
     45  * need to be implemented.
     46  *
     47  * <ul>
     48  *   <li>{@link #onIsLanguageAvailable}</li>
     49  *   <li>{@link #onLoadLanguage}</li>
     50  *   <li>{@link #onGetLanguage}</li>
     51  *   <li>{@link #onSynthesizeText}</li>
     52  *   <li>{@link #onStop}</li>
     53  * </ul>
     54  *
     55  * The first three deal primarily with language management, and are used to
     56  * query the engine for it's support for a given language and indicate to it
     57  * that requests in a given language are imminent.
     58  *
     59  * {@link #onSynthesizeText} is central to the engine implementation. The
     60  * implementation should synthesize text as per the request parameters and
     61  * return synthesized data via the supplied callback. This class and its helpers
     62  * will then consume that data, which might mean queueing it for playback or writing
     63  * it to a file or similar. All calls to this method will be on a single
     64  * thread, which will be different from the main thread of the service. Synthesis
     65  * must be synchronous which means the engine must NOT hold on the callback or call
     66  * any methods on it after the method returns
     67  *
     68  * {@link #onStop} tells the engine that it should stop all ongoing synthesis, if
     69  * any. Any pending data from the current synthesis will be discarded.
     70  *
     71  */
     72 public abstract class TextToSpeechService extends Service {
     73 
     74     private static final boolean DBG = false;
     75     private static final String TAG = "TextToSpeechService";
     76 
     77     private static final int MAX_SPEECH_ITEM_CHAR_LENGTH = 4000;
     78     private static final String SYNTH_THREAD_NAME = "SynthThread";
     79 
     80     private SynthHandler mSynthHandler;
     81     // A thread and it's associated handler for playing back any audio
     82     // associated with this TTS engine. Will handle all requests except synthesis
     83     // to file requests, which occur on the synthesis thread.
     84     private AudioPlaybackHandler mAudioPlaybackHandler;
     85     private TtsEngines mEngineHelper;
     86 
     87     private CallbackMap mCallbacks;
     88     private String mPackageName;
     89 
     90     @Override
     91     public void onCreate() {
     92         if (DBG) Log.d(TAG, "onCreate()");
     93         super.onCreate();
     94 
     95         SynthThread synthThread = new SynthThread();
     96         synthThread.start();
     97         mSynthHandler = new SynthHandler(synthThread.getLooper());
     98 
     99         mAudioPlaybackHandler = new AudioPlaybackHandler();
    100         mAudioPlaybackHandler.start();
    101 
    102         mEngineHelper = new TtsEngines(this);
    103 
    104         mCallbacks = new CallbackMap();
    105 
    106         mPackageName = getApplicationInfo().packageName;
    107 
    108         String[] defaultLocale = getSettingsLocale();
    109         // Load default language
    110         onLoadLanguage(defaultLocale[0], defaultLocale[1], defaultLocale[2]);
    111     }
    112 
    113     @Override
    114     public void onDestroy() {
    115         if (DBG) Log.d(TAG, "onDestroy()");
    116 
    117         // Tell the synthesizer to stop
    118         mSynthHandler.quit();
    119         // Tell the audio playback thread to stop.
    120         mAudioPlaybackHandler.quit();
    121         // Unregister all callbacks.
    122         mCallbacks.kill();
    123 
    124         super.onDestroy();
    125     }
    126 
    127     /**
    128      * Checks whether the engine supports a given language.
    129      *
    130      * Can be called on multiple threads.
    131      *
    132      * @param lang ISO-3 language code.
    133      * @param country ISO-3 country code. May be empty or null.
    134      * @param variant Language variant. May be empty or null.
    135      * @return Code indicating the support status for the locale.
    136      *         One of {@link TextToSpeech#LANG_AVAILABLE},
    137      *         {@link TextToSpeech#LANG_COUNTRY_AVAILABLE},
    138      *         {@link TextToSpeech#LANG_COUNTRY_VAR_AVAILABLE},
    139      *         {@link TextToSpeech#LANG_MISSING_DATA}
    140      *         {@link TextToSpeech#LANG_NOT_SUPPORTED}.
    141      */
    142     protected abstract int onIsLanguageAvailable(String lang, String country, String variant);
    143 
    144     /**
    145      * Returns the language, country and variant currently being used by the TTS engine.
    146      *
    147      * Can be called on multiple threads.
    148      *
    149      * @return A 3-element array, containing language (ISO 3-letter code),
    150      *         country (ISO 3-letter code) and variant used by the engine.
    151      *         The country and variant may be {@code ""}. If country is empty, then variant must
    152      *         be empty too.
    153      * @see Locale#getISO3Language()
    154      * @see Locale#getISO3Country()
    155      * @see Locale#getVariant()
    156      */
    157     protected abstract String[] onGetLanguage();
    158 
    159     /**
    160      * Notifies the engine that it should load a speech synthesis language. There is no guarantee
    161      * that this method is always called before the language is used for synthesis. It is merely
    162      * a hint to the engine that it will probably get some synthesis requests for this language
    163      * at some point in the future.
    164      *
    165      * Can be called on multiple threads.
    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 onLoadLanguage(String lang, String country, String variant);
    178 
    179     /**
    180      * Notifies the service that it should stop any in-progress speech synthesis.
    181      * This method can be called even if no speech synthesis is currently in progress.
    182      *
    183      * Can be called on multiple threads, but not on the synthesis thread.
    184      */
    185     protected abstract void onStop();
    186 
    187     /**
    188      * Tells the service to synthesize speech from the given text. This method should
    189      * block until the synthesis is finished.
    190      *
    191      * Called on the synthesis thread.
    192      *
    193      * @param request The synthesis request.
    194      * @param callback The callback the the engine must use to make data available for
    195      *         playback or for writing to a file.
    196      */
    197     protected abstract void onSynthesizeText(SynthesisRequest request,
    198             SynthesisCallback callback);
    199 
    200     /**
    201      * Queries the service for a set of features supported for a given language.
    202      *
    203      * @param lang ISO-3 language code.
    204      * @param country ISO-3 country code. May be empty or null.
    205      * @param variant Language variant. May be empty or null.
    206      * @return A list of features supported for the given language.
    207      */
    208     protected Set<String> onGetFeaturesForLanguage(String lang, String country, String variant) {
    209         return null;
    210     }
    211 
    212     private int getDefaultSpeechRate() {
    213         return getSecureSettingInt(Settings.Secure.TTS_DEFAULT_RATE, Engine.DEFAULT_RATE);
    214     }
    215 
    216     private String[] getSettingsLocale() {
    217         final String locale = mEngineHelper.getLocalePrefForEngine(mPackageName);
    218         return TtsEngines.parseLocalePref(locale);
    219     }
    220 
    221     private int getSecureSettingInt(String name, int defaultValue) {
    222         return Settings.Secure.getInt(getContentResolver(), name, defaultValue);
    223     }
    224 
    225     /**
    226      * Synthesizer thread. This thread is used to run {@link SynthHandler}.
    227      */
    228     private class SynthThread extends HandlerThread implements MessageQueue.IdleHandler {
    229 
    230         private boolean mFirstIdle = true;
    231 
    232         public SynthThread() {
    233             super(SYNTH_THREAD_NAME, android.os.Process.THREAD_PRIORITY_DEFAULT);
    234         }
    235 
    236         @Override
    237         protected void onLooperPrepared() {
    238             getLooper().getQueue().addIdleHandler(this);
    239         }
    240 
    241         @Override
    242         public boolean queueIdle() {
    243             if (mFirstIdle) {
    244                 mFirstIdle = false;
    245             } else {
    246                 broadcastTtsQueueProcessingCompleted();
    247             }
    248             return true;
    249         }
    250 
    251         private void broadcastTtsQueueProcessingCompleted() {
    252             Intent i = new Intent(TextToSpeech.ACTION_TTS_QUEUE_PROCESSING_COMPLETED);
    253             if (DBG) Log.d(TAG, "Broadcasting: " + i);
    254             sendBroadcast(i);
    255         }
    256     }
    257 
    258     private class SynthHandler extends Handler {
    259 
    260         private SpeechItem mCurrentSpeechItem = null;
    261 
    262         public SynthHandler(Looper looper) {
    263             super(looper);
    264         }
    265 
    266         private synchronized SpeechItem getCurrentSpeechItem() {
    267             return mCurrentSpeechItem;
    268         }
    269 
    270         private synchronized SpeechItem setCurrentSpeechItem(SpeechItem speechItem) {
    271             SpeechItem old = mCurrentSpeechItem;
    272             mCurrentSpeechItem = speechItem;
    273             return old;
    274         }
    275 
    276         private synchronized SpeechItem maybeRemoveCurrentSpeechItem(Object callerIdentity) {
    277             if (mCurrentSpeechItem != null &&
    278                     mCurrentSpeechItem.getCallerIdentity() == callerIdentity) {
    279                 SpeechItem current = mCurrentSpeechItem;
    280                 mCurrentSpeechItem = null;
    281                 return current;
    282             }
    283 
    284             return null;
    285         }
    286 
    287         public boolean isSpeaking() {
    288             return getCurrentSpeechItem() != null;
    289         }
    290 
    291         public void quit() {
    292             // Don't process any more speech items
    293             getLooper().quit();
    294             // Stop the current speech item
    295             SpeechItem current = setCurrentSpeechItem(null);
    296             if (current != null) {
    297                 current.stop();
    298             }
    299 
    300             // The AudioPlaybackHandler will be destroyed by the caller.
    301         }
    302 
    303         /**
    304          * Adds a speech item to the queue.
    305          *
    306          * Called on a service binder thread.
    307          */
    308         public int enqueueSpeechItem(int queueMode, final SpeechItem speechItem) {
    309             if (!speechItem.isValid()) {
    310                 speechItem.dispatchOnError();
    311                 return TextToSpeech.ERROR;
    312             }
    313 
    314             if (queueMode == TextToSpeech.QUEUE_FLUSH) {
    315                 stopForApp(speechItem.getCallerIdentity());
    316             } else if (queueMode == TextToSpeech.QUEUE_DESTROY) {
    317                 stopAll();
    318             }
    319             Runnable runnable = new Runnable() {
    320                 @Override
    321                 public void run() {
    322                     setCurrentSpeechItem(speechItem);
    323                     speechItem.play();
    324                     setCurrentSpeechItem(null);
    325                 }
    326             };
    327             Message msg = Message.obtain(this, runnable);
    328             // The obj is used to remove all callbacks from the given app in
    329             // stopForApp(String).
    330             //
    331             // Note that this string is interned, so the == comparison works.
    332             msg.obj = speechItem.getCallerIdentity();
    333             if (sendMessage(msg)) {
    334                 return TextToSpeech.SUCCESS;
    335             } else {
    336                 Log.w(TAG, "SynthThread has quit");
    337                 speechItem.dispatchOnError();
    338                 return TextToSpeech.ERROR;
    339             }
    340         }
    341 
    342         /**
    343          * Stops all speech output and removes any utterances still in the queue for
    344          * the calling app.
    345          *
    346          * Called on a service binder thread.
    347          */
    348         public int stopForApp(Object callerIdentity) {
    349             if (callerIdentity == null) {
    350                 return TextToSpeech.ERROR;
    351             }
    352 
    353             removeCallbacksAndMessages(callerIdentity);
    354             // This stops writing data to the file / or publishing
    355             // items to the audio playback handler.
    356             //
    357             // Note that the current speech item must be removed only if it
    358             // belongs to the callingApp, else the item will be "orphaned" and
    359             // not stopped correctly if a stop request comes along for the item
    360             // from the app it belongs to.
    361             SpeechItem current = maybeRemoveCurrentSpeechItem(callerIdentity);
    362             if (current != null) {
    363                 current.stop();
    364             }
    365 
    366             // Remove any enqueued audio too.
    367             mAudioPlaybackHandler.stopForApp(callerIdentity);
    368 
    369             return TextToSpeech.SUCCESS;
    370         }
    371 
    372         public int stopAll() {
    373             // Stop the current speech item unconditionally.
    374             SpeechItem current = setCurrentSpeechItem(null);
    375             if (current != null) {
    376                 current.stop();
    377             }
    378             // Remove all other items from the queue.
    379             removeCallbacksAndMessages(null);
    380             // Remove all pending playback as well.
    381             mAudioPlaybackHandler.stop();
    382 
    383             return TextToSpeech.SUCCESS;
    384         }
    385     }
    386 
    387     interface UtteranceProgressDispatcher {
    388         public void dispatchOnDone();
    389         public void dispatchOnStart();
    390         public void dispatchOnError();
    391     }
    392 
    393     /**
    394      * An item in the synth thread queue.
    395      */
    396     private abstract class SpeechItem implements UtteranceProgressDispatcher {
    397         private final Object mCallerIdentity;
    398         protected final Bundle mParams;
    399         private final int mCallerUid;
    400         private final int mCallerPid;
    401         private boolean mStarted = false;
    402         private boolean mStopped = false;
    403 
    404         public SpeechItem(Object caller, int callerUid, int callerPid, Bundle params) {
    405             mCallerIdentity = caller;
    406             mParams = params;
    407             mCallerUid = callerUid;
    408             mCallerPid = callerPid;
    409         }
    410 
    411         public Object getCallerIdentity() {
    412             return mCallerIdentity;
    413         }
    414 
    415         /**
    416          * Checker whether the item is valid. If this method returns false, the item should not
    417          * be played.
    418          */
    419         public abstract boolean isValid();
    420 
    421         /**
    422          * Plays the speech item. Blocks until playback is finished.
    423          * Must not be called more than once.
    424          *
    425          * Only called on the synthesis thread.
    426          *
    427          * @return {@link TextToSpeech#SUCCESS} or {@link TextToSpeech#ERROR}.
    428          */
    429         public int play() {
    430             synchronized (this) {
    431                 if (mStarted) {
    432                     throw new IllegalStateException("play() called twice");
    433                 }
    434                 mStarted = true;
    435             }
    436             return playImpl();
    437         }
    438 
    439         /**
    440          * Stops the speech item.
    441          * Must not be called more than once.
    442          *
    443          * Can be called on multiple threads,  but not on the synthesis thread.
    444          */
    445         public void stop() {
    446             synchronized (this) {
    447                 if (mStopped) {
    448                     throw new IllegalStateException("stop() called twice");
    449                 }
    450                 mStopped = true;
    451             }
    452             stopImpl();
    453         }
    454 
    455         @Override
    456         public void dispatchOnDone() {
    457             final String utteranceId = getUtteranceId();
    458             if (utteranceId != null) {
    459                 mCallbacks.dispatchOnDone(getCallerIdentity(), utteranceId);
    460             }
    461         }
    462 
    463         @Override
    464         public void dispatchOnStart() {
    465             final String utteranceId = getUtteranceId();
    466             if (utteranceId != null) {
    467                 mCallbacks.dispatchOnStart(getCallerIdentity(), utteranceId);
    468             }
    469         }
    470 
    471         @Override
    472         public void dispatchOnError() {
    473             final String utteranceId = getUtteranceId();
    474             if (utteranceId != null) {
    475                 mCallbacks.dispatchOnError(getCallerIdentity(), utteranceId);
    476             }
    477         }
    478 
    479         public int getCallerUid() {
    480             return mCallerUid;
    481         }
    482 
    483         public int getCallerPid() {
    484             return mCallerPid;
    485         }
    486 
    487         protected synchronized boolean isStopped() {
    488              return mStopped;
    489         }
    490 
    491         protected abstract int playImpl();
    492 
    493         protected abstract void stopImpl();
    494 
    495         public int getStreamType() {
    496             return getIntParam(Engine.KEY_PARAM_STREAM, Engine.DEFAULT_STREAM);
    497         }
    498 
    499         public float getVolume() {
    500             return getFloatParam(Engine.KEY_PARAM_VOLUME, Engine.DEFAULT_VOLUME);
    501         }
    502 
    503         public float getPan() {
    504             return getFloatParam(Engine.KEY_PARAM_PAN, Engine.DEFAULT_PAN);
    505         }
    506 
    507         public String getUtteranceId() {
    508             return getStringParam(Engine.KEY_PARAM_UTTERANCE_ID, null);
    509         }
    510 
    511         protected String getStringParam(String key, String defaultValue) {
    512             return mParams == null ? defaultValue : mParams.getString(key, defaultValue);
    513         }
    514 
    515         protected int getIntParam(String key, int defaultValue) {
    516             return mParams == null ? defaultValue : mParams.getInt(key, defaultValue);
    517         }
    518 
    519         protected float getFloatParam(String key, float defaultValue) {
    520             return mParams == null ? defaultValue : mParams.getFloat(key, defaultValue);
    521         }
    522     }
    523 
    524     class SynthesisSpeechItem extends SpeechItem {
    525         // Never null.
    526         private final String mText;
    527         private final SynthesisRequest mSynthesisRequest;
    528         private final String[] mDefaultLocale;
    529         // Non null after synthesis has started, and all accesses
    530         // guarded by 'this'.
    531         private AbstractSynthesisCallback mSynthesisCallback;
    532         private final EventLogger mEventLogger;
    533 
    534         public SynthesisSpeechItem(Object callerIdentity, int callerUid, int callerPid,
    535                 Bundle params, String text) {
    536             super(callerIdentity, callerUid, callerPid, params);
    537             mText = text;
    538             mSynthesisRequest = new SynthesisRequest(mText, mParams);
    539             mDefaultLocale = getSettingsLocale();
    540             setRequestParams(mSynthesisRequest);
    541             mEventLogger = new EventLogger(mSynthesisRequest, callerUid, callerPid,
    542                     mPackageName);
    543         }
    544 
    545         public String getText() {
    546             return mText;
    547         }
    548 
    549         @Override
    550         public boolean isValid() {
    551             if (mText == null) {
    552                 Log.wtf(TAG, "Got null text");
    553                 return false;
    554             }
    555             if (mText.length() >= MAX_SPEECH_ITEM_CHAR_LENGTH) {
    556                 Log.w(TAG, "Text too long: " + mText.length() + " chars");
    557                 return false;
    558             }
    559             return true;
    560         }
    561 
    562         @Override
    563         protected int playImpl() {
    564             AbstractSynthesisCallback synthesisCallback;
    565             mEventLogger.onRequestProcessingStart();
    566             synchronized (this) {
    567                 // stop() might have been called before we enter this
    568                 // synchronized block.
    569                 if (isStopped()) {
    570                     return TextToSpeech.ERROR;
    571                 }
    572                 mSynthesisCallback = createSynthesisCallback();
    573                 synthesisCallback = mSynthesisCallback;
    574             }
    575             TextToSpeechService.this.onSynthesizeText(mSynthesisRequest, synthesisCallback);
    576             return synthesisCallback.isDone() ? TextToSpeech.SUCCESS : TextToSpeech.ERROR;
    577         }
    578 
    579         protected AbstractSynthesisCallback createSynthesisCallback() {
    580             return new PlaybackSynthesisCallback(getStreamType(), getVolume(), getPan(),
    581                     mAudioPlaybackHandler, this, getCallerIdentity(), mEventLogger);
    582         }
    583 
    584         private void setRequestParams(SynthesisRequest request) {
    585             request.setLanguage(getLanguage(), getCountry(), getVariant());
    586             request.setSpeechRate(getSpeechRate());
    587 
    588             request.setPitch(getPitch());
    589         }
    590 
    591         @Override
    592         protected void stopImpl() {
    593             AbstractSynthesisCallback synthesisCallback;
    594             synchronized (this) {
    595                 synthesisCallback = mSynthesisCallback;
    596             }
    597             if (synthesisCallback != null) {
    598                 // If the synthesis callback is null, it implies that we haven't
    599                 // entered the synchronized(this) block in playImpl which in
    600                 // turn implies that synthesis would not have started.
    601                 synthesisCallback.stop();
    602                 TextToSpeechService.this.onStop();
    603             }
    604         }
    605 
    606         public String getLanguage() {
    607             return getStringParam(Engine.KEY_PARAM_LANGUAGE, mDefaultLocale[0]);
    608         }
    609 
    610         private boolean hasLanguage() {
    611             return !TextUtils.isEmpty(getStringParam(Engine.KEY_PARAM_LANGUAGE, null));
    612         }
    613 
    614         private String getCountry() {
    615             if (!hasLanguage()) return mDefaultLocale[1];
    616             return getStringParam(Engine.KEY_PARAM_COUNTRY, "");
    617         }
    618 
    619         private String getVariant() {
    620             if (!hasLanguage()) return mDefaultLocale[2];
    621             return getStringParam(Engine.KEY_PARAM_VARIANT, "");
    622         }
    623 
    624         private int getSpeechRate() {
    625             return getIntParam(Engine.KEY_PARAM_RATE, getDefaultSpeechRate());
    626         }
    627 
    628         private int getPitch() {
    629             return getIntParam(Engine.KEY_PARAM_PITCH, Engine.DEFAULT_PITCH);
    630         }
    631     }
    632 
    633     private class SynthesisToFileSpeechItem extends SynthesisSpeechItem {
    634         private final File mFile;
    635 
    636         public SynthesisToFileSpeechItem(Object callerIdentity, int callerUid, int callerPid,
    637                 Bundle params, String text,
    638                 File file) {
    639             super(callerIdentity, callerUid, callerPid, params, text);
    640             mFile = file;
    641         }
    642 
    643         @Override
    644         public boolean isValid() {
    645             if (!super.isValid()) {
    646                 return false;
    647             }
    648             return checkFile(mFile);
    649         }
    650 
    651         @Override
    652         protected AbstractSynthesisCallback createSynthesisCallback() {
    653             return new FileSynthesisCallback(mFile);
    654         }
    655 
    656         @Override
    657         protected int playImpl() {
    658             dispatchOnStart();
    659             int status = super.playImpl();
    660             if (status == TextToSpeech.SUCCESS) {
    661                 dispatchOnDone();
    662             } else {
    663                 dispatchOnError();
    664             }
    665             return status;
    666         }
    667 
    668         /**
    669          * Checks that the given file can be used for synthesis output.
    670          */
    671         private boolean checkFile(File file) {
    672             try {
    673                 if (file.exists()) {
    674                     Log.v(TAG, "File " + file + " exists, deleting.");
    675                     if (!file.delete()) {
    676                         Log.e(TAG, "Failed to delete " + file);
    677                         return false;
    678                     }
    679                 }
    680                 if (!file.createNewFile()) {
    681                     Log.e(TAG, "Can't create file " + file);
    682                     return false;
    683                 }
    684                 if (!file.delete()) {
    685                     Log.e(TAG, "Failed to delete " + file);
    686                     return false;
    687                 }
    688                 return true;
    689             } catch (IOException e) {
    690                 Log.e(TAG, "Can't use " + file + " due to exception " + e);
    691                 return false;
    692             }
    693         }
    694     }
    695 
    696     private class AudioSpeechItem extends SpeechItem {
    697         private final AudioPlaybackQueueItem mItem;
    698         public AudioSpeechItem(Object callerIdentity, int callerUid, int callerPid,
    699                 Bundle params, Uri uri) {
    700             super(callerIdentity, callerUid, callerPid, params);
    701             mItem = new AudioPlaybackQueueItem(this, getCallerIdentity(),
    702                     TextToSpeechService.this, uri, getStreamType());
    703         }
    704 
    705         @Override
    706         public boolean isValid() {
    707             return true;
    708         }
    709 
    710         @Override
    711         protected int playImpl() {
    712             mAudioPlaybackHandler.enqueue(mItem);
    713             return TextToSpeech.SUCCESS;
    714         }
    715 
    716         @Override
    717         protected void stopImpl() {
    718             // Do nothing.
    719         }
    720     }
    721 
    722     private class SilenceSpeechItem extends SpeechItem {
    723         private final long mDuration;
    724 
    725         public SilenceSpeechItem(Object callerIdentity, int callerUid, int callerPid,
    726                 Bundle params, long duration) {
    727             super(callerIdentity, callerUid, callerPid, params);
    728             mDuration = duration;
    729         }
    730 
    731         @Override
    732         public boolean isValid() {
    733             return true;
    734         }
    735 
    736         @Override
    737         protected int playImpl() {
    738             mAudioPlaybackHandler.enqueue(new SilencePlaybackQueueItem(
    739                     this, getCallerIdentity(), mDuration));
    740             return TextToSpeech.SUCCESS;
    741         }
    742 
    743         @Override
    744         protected void stopImpl() {
    745             // Do nothing, handled by AudioPlaybackHandler#stopForApp
    746         }
    747     }
    748 
    749     @Override
    750     public IBinder onBind(Intent intent) {
    751         if (TextToSpeech.Engine.INTENT_ACTION_TTS_SERVICE.equals(intent.getAction())) {
    752             return mBinder;
    753         }
    754         return null;
    755     }
    756 
    757     /**
    758      * Binder returned from {@code #onBind(Intent)}. The methods in this class can be
    759      * called called from several different threads.
    760      */
    761     // NOTE: All calls that are passed in a calling app are interned so that
    762     // they can be used as message objects (which are tested for equality using ==).
    763     private final ITextToSpeechService.Stub mBinder = new ITextToSpeechService.Stub() {
    764         @Override
    765         public int speak(IBinder caller, String text, int queueMode, Bundle params) {
    766             if (!checkNonNull(caller, text, params)) {
    767                 return TextToSpeech.ERROR;
    768             }
    769 
    770             SpeechItem item = new SynthesisSpeechItem(caller,
    771                     Binder.getCallingUid(), Binder.getCallingPid(), params, text);
    772             return mSynthHandler.enqueueSpeechItem(queueMode, item);
    773         }
    774 
    775         @Override
    776         public int synthesizeToFile(IBinder caller, String text, String filename,
    777                 Bundle params) {
    778             if (!checkNonNull(caller, text, filename, params)) {
    779                 return TextToSpeech.ERROR;
    780             }
    781 
    782             File file = new File(filename);
    783             SpeechItem item = new SynthesisToFileSpeechItem(caller, Binder.getCallingUid(),
    784                     Binder.getCallingPid(), params, text, file);
    785             return mSynthHandler.enqueueSpeechItem(TextToSpeech.QUEUE_ADD, item);
    786         }
    787 
    788         @Override
    789         public int playAudio(IBinder caller, Uri audioUri, int queueMode, Bundle params) {
    790             if (!checkNonNull(caller, audioUri, params)) {
    791                 return TextToSpeech.ERROR;
    792             }
    793 
    794             SpeechItem item = new AudioSpeechItem(caller,
    795                     Binder.getCallingUid(), Binder.getCallingPid(), params, audioUri);
    796             return mSynthHandler.enqueueSpeechItem(queueMode, item);
    797         }
    798 
    799         @Override
    800         public int playSilence(IBinder caller, long duration, int queueMode, Bundle params) {
    801             if (!checkNonNull(caller, params)) {
    802                 return TextToSpeech.ERROR;
    803             }
    804 
    805             SpeechItem item = new SilenceSpeechItem(caller,
    806                     Binder.getCallingUid(), Binder.getCallingPid(), params, duration);
    807             return mSynthHandler.enqueueSpeechItem(queueMode, item);
    808         }
    809 
    810         @Override
    811         public boolean isSpeaking() {
    812             return mSynthHandler.isSpeaking() || mAudioPlaybackHandler.isSpeaking();
    813         }
    814 
    815         @Override
    816         public int stop(IBinder caller) {
    817             if (!checkNonNull(caller)) {
    818                 return TextToSpeech.ERROR;
    819             }
    820 
    821             return mSynthHandler.stopForApp(caller);
    822         }
    823 
    824         @Override
    825         public String[] getLanguage() {
    826             return onGetLanguage();
    827         }
    828 
    829         /*
    830          * If defaults are enforced, then no language is "available" except
    831          * perhaps the default language selected by the user.
    832          */
    833         @Override
    834         public int isLanguageAvailable(String lang, String country, String variant) {
    835             if (!checkNonNull(lang)) {
    836                 return TextToSpeech.ERROR;
    837             }
    838 
    839             return onIsLanguageAvailable(lang, country, variant);
    840         }
    841 
    842         @Override
    843         public String[] getFeaturesForLanguage(String lang, String country, String variant) {
    844             Set<String> features = onGetFeaturesForLanguage(lang, country, variant);
    845             String[] featuresArray = null;
    846             if (features != null) {
    847                 featuresArray = new String[features.size()];
    848                 features.toArray(featuresArray);
    849             } else {
    850                 featuresArray = new String[0];
    851             }
    852             return featuresArray;
    853         }
    854 
    855         /*
    856          * There is no point loading a non default language if defaults
    857          * are enforced.
    858          */
    859         @Override
    860         public int loadLanguage(String lang, String country, String variant) {
    861             if (!checkNonNull(lang)) {
    862                 return TextToSpeech.ERROR;
    863             }
    864 
    865             return onLoadLanguage(lang, country, variant);
    866         }
    867 
    868         @Override
    869         public void setCallback(IBinder caller, ITextToSpeechCallback cb) {
    870             // Note that passing in a null callback is a valid use case.
    871             if (!checkNonNull(caller)) {
    872                 return;
    873             }
    874 
    875             mCallbacks.setCallback(caller, cb);
    876         }
    877 
    878         private String intern(String in) {
    879             // The input parameter will be non null.
    880             return in.intern();
    881         }
    882 
    883         private boolean checkNonNull(Object... args) {
    884             for (Object o : args) {
    885                 if (o == null) return false;
    886             }
    887             return true;
    888         }
    889     };
    890 
    891     private class CallbackMap extends RemoteCallbackList<ITextToSpeechCallback> {
    892         private final HashMap<IBinder, ITextToSpeechCallback> mCallerToCallback
    893                 = new HashMap<IBinder, ITextToSpeechCallback>();
    894 
    895         public void setCallback(IBinder caller, ITextToSpeechCallback cb) {
    896             synchronized (mCallerToCallback) {
    897                 ITextToSpeechCallback old;
    898                 if (cb != null) {
    899                     register(cb, caller);
    900                     old = mCallerToCallback.put(caller, cb);
    901                 } else {
    902                     old = mCallerToCallback.remove(caller);
    903                 }
    904                 if (old != null && old != cb) {
    905                     unregister(old);
    906                 }
    907             }
    908         }
    909 
    910         public void dispatchOnDone(Object callerIdentity, String utteranceId) {
    911             ITextToSpeechCallback cb = getCallbackFor(callerIdentity);
    912             if (cb == null) return;
    913             try {
    914                 cb.onDone(utteranceId);
    915             } catch (RemoteException e) {
    916                 Log.e(TAG, "Callback onDone failed: " + e);
    917             }
    918         }
    919 
    920         public void dispatchOnStart(Object callerIdentity, String utteranceId) {
    921             ITextToSpeechCallback cb = getCallbackFor(callerIdentity);
    922             if (cb == null) return;
    923             try {
    924                 cb.onStart(utteranceId);
    925             } catch (RemoteException e) {
    926                 Log.e(TAG, "Callback onStart failed: " + e);
    927             }
    928 
    929         }
    930 
    931         public void dispatchOnError(Object callerIdentity, String utteranceId) {
    932             ITextToSpeechCallback cb = getCallbackFor(callerIdentity);
    933             if (cb == null) return;
    934             try {
    935                 cb.onError(utteranceId);
    936             } catch (RemoteException e) {
    937                 Log.e(TAG, "Callback onError failed: " + e);
    938             }
    939         }
    940 
    941         @Override
    942         public void onCallbackDied(ITextToSpeechCallback callback, Object cookie) {
    943             IBinder caller = (IBinder) cookie;
    944             synchronized (mCallerToCallback) {
    945                 mCallerToCallback.remove(caller);
    946             }
    947             mSynthHandler.stopForApp(caller);
    948         }
    949 
    950         @Override
    951         public void kill() {
    952             synchronized (mCallerToCallback) {
    953                 mCallerToCallback.clear();
    954                 super.kill();
    955             }
    956         }
    957 
    958         private ITextToSpeechCallback getCallbackFor(Object caller) {
    959             ITextToSpeechCallback cb;
    960             IBinder asBinder = (IBinder) caller;
    961             synchronized (mCallerToCallback) {
    962                 cb = mCallerToCallback.get(asBinder);
    963             }
    964 
    965             return cb;
    966         }
    967 
    968     }
    969 
    970 }
    971