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