blob: 575855c1f1937d40ebe39cfc8bb195e9dea90ba5 [file] [log] [blame]
Bjorn Bringert50e657b2011-03-08 16:00:40 +00001/*
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 */
16package android.speech.tts;
17
18import android.app.Service;
19import android.content.Intent;
20import android.net.Uri;
Narayan Kamath492b7f02011-11-29 17:02:06 +000021import android.os.Binder;
Bjorn Bringert50e657b2011-03-08 16:00:40 +000022import android.os.Bundle;
Bjorn Bringert50e657b2011-03-08 16:00:40 +000023import android.os.Handler;
24import android.os.HandlerThread;
25import android.os.IBinder;
26import android.os.Looper;
27import android.os.Message;
28import android.os.MessageQueue;
Przemyslaw Szczepaniak5acb33a2013-02-08 16:36:25 +000029import android.os.ParcelFileDescriptor;
Bjorn Bringert50e657b2011-03-08 16:00:40 +000030import android.os.RemoteCallbackList;
31import android.os.RemoteException;
32import android.provider.Settings;
33import android.speech.tts.TextToSpeech.Engine;
34import android.text.TextUtils;
35import android.util.Log;
36
Przemyslaw Szczepaniak5acb33a2013-02-08 16:36:25 +000037import java.io.FileDescriptor;
38import java.io.FileOutputStream;
Przemyslaw Szczepaniakfcf671b2013-03-05 11:28:50 +000039import java.io.IOException;
Bjorn Bringert50e657b2011-03-08 16:00:40 +000040import java.util.HashMap;
41import java.util.Locale;
Narayan Kamath748af662011-10-31 14:20:01 +000042import java.util.Set;
Bjorn Bringert50e657b2011-03-08 16:00:40 +000043
44
45/**
Narayan Kamathe22b69a2011-06-08 11:41:47 +010046 * 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.
Narayan Kamathe5b8c4d2011-08-22 15:37:47 +010072 *
Bjorn Bringert50e657b2011-03-08 16:00:40 +000073 */
74public abstract class TextToSpeechService extends Service {
75
76 private static final boolean DBG = false;
77 private static final String TAG = "TextToSpeechService";
78
Przemyslaw Szczepaniak2d940bc2012-11-19 12:22:59 +000079
Bjorn Bringert50e657b2011-03-08 16:00:40 +000080 private static final String SYNTH_THREAD_NAME = "SynthThread";
81
82 private SynthHandler mSynthHandler;
Narayan Kamath8d1fc242011-06-03 18:11:54 +010083 // 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;
Narayan Kamathe5b8c4d2011-08-22 15:37:47 +010087 private TtsEngines mEngineHelper;
Bjorn Bringert50e657b2011-03-08 16:00:40 +000088
89 private CallbackMap mCallbacks;
Narayan Kamath6dabb632011-07-08 12:13:03 +010090 private String mPackageName;
Narayan Kamath7a3af862011-06-02 17:28:57 +010091
Bjorn Bringert50e657b2011-03-08 16:00:40 +000092 @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
Narayan Kamath4924fe32011-06-09 11:35:13 +0100101 mAudioPlaybackHandler = new AudioPlaybackHandler();
102 mAudioPlaybackHandler.start();
Narayan Kamathc90f1c82011-05-24 11:39:43 +0100103
Narayan Kamathe5b8c4d2011-08-22 15:37:47 +0100104 mEngineHelper = new TtsEngines(this);
105
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000106 mCallbacks = new CallbackMap();
107
Narayan Kamath6dabb632011-07-08 12:13:03 +0100108 mPackageName = getApplicationInfo().packageName;
109
Narayan Kamathe5b8c4d2011-08-22 15:37:47 +0100110 String[] defaultLocale = getSettingsLocale();
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000111 // Load default language
Narayan Kamathe5b8c4d2011-08-22 15:37:47 +0100112 onLoadLanguage(defaultLocale[0], defaultLocale[1], defaultLocale[2]);
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000113 }
114
115 @Override
116 public void onDestroy() {
117 if (DBG) Log.d(TAG, "onDestroy()");
118
119 // Tell the synthesizer to stop
120 mSynthHandler.quit();
Narayan Kamath8d1fc242011-06-03 18:11:54 +0100121 // Tell the audio playback thread to stop.
122 mAudioPlaybackHandler.quit();
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000123 // 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 *
Przemyslaw Szczepaniak13896b72012-11-09 15:18:16 +0000134 * Its return values HAVE to be consistent with onLoadLanguage.
135 *
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000136 * @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.
Przemyslaw Szczepaniak13896b72012-11-09 15:18:16 +0000170 * 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.
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000172 *
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 *
Narayan Kamathe22b69a2011-06-08 11:41:47 +0100199 * @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.
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000202 */
Narayan Kamathe22b69a2011-06-08 11:41:47 +0100203 protected abstract void onSynthesizeText(SynthesisRequest request,
204 SynthesisCallback callback);
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000205
Narayan Kamath748af662011-10-31 14:20:01 +0000206 /**
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
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000218 private int getDefaultSpeechRate() {
219 return getSecureSettingInt(Settings.Secure.TTS_DEFAULT_RATE, Engine.DEFAULT_RATE);
220 }
221
Narayan Kamathe5b8c4d2011-08-22 15:37:47 +0100222 private String[] getSettingsLocale() {
223 final String locale = mEngineHelper.getLocalePrefForEngine(mPackageName);
224 return TtsEngines.parseLocalePref(locale);
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000225 }
226
227 private int getSecureSettingInt(String name, int defaultValue) {
228 return Settings.Secure.getInt(getContentResolver(), name, defaultValue);
229 }
230
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000231 /**
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() {
Narayan Kamath84deb602011-07-27 13:22:09 +0100239 super(SYNTH_THREAD_NAME, android.os.Process.THREAD_PRIORITY_DEFAULT);
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000240 }
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 {
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000265 private SpeechItem mCurrentSpeechItem = null;
266
267 public SynthHandler(Looper looper) {
268 super(looper);
269 }
270
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000271 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
Narayan Kamath492b7f02011-11-29 17:02:06 +0000281 private synchronized SpeechItem maybeRemoveCurrentSpeechItem(Object callerIdentity) {
Narayan Kamatha65c62a2011-09-06 17:04:48 +0100282 if (mCurrentSpeechItem != null &&
Przemyslaw Szczepaniak13896b72012-11-09 15:18:16 +0000283 (mCurrentSpeechItem.getCallerIdentity() == callerIdentity)) {
Narayan Kamatha65c62a2011-09-06 17:04:48 +0100284 SpeechItem current = mCurrentSpeechItem;
285 mCurrentSpeechItem = null;
286 return current;
287 }
288
289 return null;
290 }
291
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000292 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 }
Narayan Kamathbe4ad4a2011-07-15 13:01:09 +0100304 // The AudioPlaybackHandler will be destroyed by the caller.
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000305 }
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) {
Przemyslaw Szczepaniak13896b72012-11-09 15:18:16 +0000313 UtteranceProgressDispatcher utterenceProgress = null;
314 if (speechItem instanceof UtteranceProgressDispatcher) {
315 utterenceProgress = (UtteranceProgressDispatcher) speechItem;
316 }
317
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000318 if (!speechItem.isValid()) {
Przemyslaw Szczepaniak13896b72012-11-09 15:18:16 +0000319 if (utterenceProgress != null) {
320 utterenceProgress.dispatchOnError();
321 }
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000322 return TextToSpeech.ERROR;
323 }
Narayan Kamath4924fe32011-06-09 11:35:13 +0100324
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000325 if (queueMode == TextToSpeech.QUEUE_FLUSH) {
Narayan Kamath492b7f02011-11-29 17:02:06 +0000326 stopForApp(speechItem.getCallerIdentity());
Narayan Kamathabc63fb2011-06-10 11:36:57 +0100327 } else if (queueMode == TextToSpeech.QUEUE_DESTROY) {
Narayan Kamatha65c62a2011-09-06 17:04:48 +0100328 stopAll();
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000329 }
330 Runnable runnable = new Runnable() {
331 @Override
332 public void run() {
333 setCurrentSpeechItem(speechItem);
Narayan Kamath8d1fc242011-06-03 18:11:54 +0100334 speechItem.play();
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000335 setCurrentSpeechItem(null);
336 }
337 };
338 Message msg = Message.obtain(this, runnable);
Przemyslaw Szczepaniak13896b72012-11-09 15:18:16 +0000339
Narayan Kamatha65c62a2011-09-06 17:04:48 +0100340 // The obj is used to remove all callbacks from the given app in
341 // stopForApp(String).
Narayan Kamathabc63fb2011-06-10 11:36:57 +0100342 //
343 // Note that this string is interned, so the == comparison works.
Narayan Kamath492b7f02011-11-29 17:02:06 +0000344 msg.obj = speechItem.getCallerIdentity();
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000345 if (sendMessage(msg)) {
346 return TextToSpeech.SUCCESS;
347 } else {
348 Log.w(TAG, "SynthThread has quit");
Przemyslaw Szczepaniak13896b72012-11-09 15:18:16 +0000349 if (utterenceProgress != null) {
350 utterenceProgress.dispatchOnError();
351 }
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000352 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 */
Narayan Kamath492b7f02011-11-29 17:02:06 +0000362 public int stopForApp(Object callerIdentity) {
363 if (callerIdentity == null) {
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000364 return TextToSpeech.ERROR;
365 }
Narayan Kamath4924fe32011-06-09 11:35:13 +0100366
Narayan Kamath492b7f02011-11-29 17:02:06 +0000367 removeCallbacksAndMessages(callerIdentity);
Narayan Kamathbe4ad4a2011-07-15 13:01:09 +0100368 // This stops writing data to the file / or publishing
369 // items to the audio playback handler.
Narayan Kamatha65c62a2011-09-06 17:04:48 +0100370 //
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.
Narayan Kamath492b7f02011-11-29 17:02:06 +0000375 SpeechItem current = maybeRemoveCurrentSpeechItem(callerIdentity);
Narayan Kamatha65c62a2011-09-06 17:04:48 +0100376 if (current != null) {
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000377 current.stop();
378 }
Narayan Kamath8d1fc242011-06-03 18:11:54 +0100379
Narayan Kamath4924fe32011-06-09 11:35:13 +0100380 // Remove any enqueued audio too.
Narayan Kamath67ae6bc2011-11-30 14:51:00 +0000381 mAudioPlaybackHandler.stopForApp(callerIdentity);
Narayan Kamath4924fe32011-06-09 11:35:13 +0100382
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000383 return TextToSpeech.SUCCESS;
384 }
Narayan Kamatha65c62a2011-09-06 17:04:48 +0100385
386 public int stopAll() {
Przemyslaw Szczepaniak13896b72012-11-09 15:18:16 +0000387 // Stop the current speech item unconditionally .
Narayan Kamatha65c62a2011-09-06 17:04:48 +0100388 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.
Narayan Kamath67ae6bc2011-11-30 14:51:00 +0000395 mAudioPlaybackHandler.stop();
Narayan Kamatha65c62a2011-09-06 17:04:48 +0100396
397 return TextToSpeech.SUCCESS;
398 }
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000399 }
400
Narayan Kamath754c72e2011-11-09 14:22:32 +0000401 interface UtteranceProgressDispatcher {
402 public void dispatchOnDone();
403 public void dispatchOnStart();
404 public void dispatchOnError();
Narayan Kamath8d1fc242011-06-03 18:11:54 +0100405 }
406
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000407 /**
408 * An item in the synth thread queue.
409 */
Przemyslaw Szczepaniak13896b72012-11-09 15:18:16 +0000410 private abstract class SpeechItem {
Narayan Kamath492b7f02011-11-29 17:02:06 +0000411 private final Object mCallerIdentity;
Narayan Kamathb956f372011-05-16 16:51:44 +0100412 protected final Bundle mParams;
Narayan Kamath492b7f02011-11-29 17:02:06 +0000413 private final int mCallerUid;
414 private final int mCallerPid;
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000415 private boolean mStarted = false;
416 private boolean mStopped = false;
417
Narayan Kamath492b7f02011-11-29 17:02:06 +0000418 public SpeechItem(Object caller, int callerUid, int callerPid, Bundle params) {
419 mCallerIdentity = caller;
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000420 mParams = params;
Narayan Kamath492b7f02011-11-29 17:02:06 +0000421 mCallerUid = callerUid;
422 mCallerPid = callerPid;
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000423 }
424
Narayan Kamath492b7f02011-11-29 17:02:06 +0000425 public Object getCallerIdentity() {
426 return mCallerIdentity;
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000427 }
428
Przemyslaw Szczepaniak13896b72012-11-09 15:18:16 +0000429
430 public int getCallerUid() {
431 return mCallerUid;
432 }
433
434 public int getCallerPid() {
435 return mCallerPid;
436 }
437
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000438 /**
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
Przemyslaw Szczepaniak13896b72012-11-09 15:18:16 +0000462 protected abstract int playImpl();
463
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000464 /**
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
Przemyslaw Szczepaniak13896b72012-11-09 15:18:16 +0000480 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
Narayan Kamath754c72e2011-11-09 14:22:32 +0000497 @Override
498 public void dispatchOnDone() {
Narayan Kamath8d1fc242011-06-03 18:11:54 +0100499 final String utteranceId = getUtteranceId();
Narayan Kamath68e2af52011-11-28 17:10:04 +0000500 if (utteranceId != null) {
Narayan Kamath492b7f02011-11-29 17:02:06 +0000501 mCallbacks.dispatchOnDone(getCallerIdentity(), utteranceId);
Narayan Kamath754c72e2011-11-09 14:22:32 +0000502 }
503 }
504
505 @Override
506 public void dispatchOnStart() {
507 final String utteranceId = getUtteranceId();
Narayan Kamath68e2af52011-11-28 17:10:04 +0000508 if (utteranceId != null) {
Narayan Kamath492b7f02011-11-29 17:02:06 +0000509 mCallbacks.dispatchOnStart(getCallerIdentity(), utteranceId);
Narayan Kamath754c72e2011-11-09 14:22:32 +0000510 }
511 }
512
513 @Override
514 public void dispatchOnError() {
515 final String utteranceId = getUtteranceId();
Narayan Kamath68e2af52011-11-28 17:10:04 +0000516 if (utteranceId != null) {
Narayan Kamath492b7f02011-11-29 17:02:06 +0000517 mCallbacks.dispatchOnError(getCallerIdentity(), utteranceId);
Narayan Kamath8d1fc242011-06-03 18:11:54 +0100518 }
519 }
520
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000521 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 }
Przemyslaw Szczepaniak13896b72012-11-09 15:18:16 +0000548
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000549 }
550
Przemyslaw Szczepaniak13896b72012-11-09 15:18:16 +0000551 class SynthesisSpeechItem extends UtteranceSpeechItem {
Narayan Kamath40f71f02011-11-23 16:42:53 +0000552 // Never null.
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000553 private final String mText;
Narayan Kamathe22b69a2011-06-08 11:41:47 +0100554 private final SynthesisRequest mSynthesisRequest;
Narayan Kamathe5b8c4d2011-08-22 15:37:47 +0100555 private final String[] mDefaultLocale;
Narayan Kamathe22b69a2011-06-08 11:41:47 +0100556 // Non null after synthesis has started, and all accesses
557 // guarded by 'this'.
558 private AbstractSynthesisCallback mSynthesisCallback;
Narayan Kamath6dabb632011-07-08 12:13:03 +0100559 private final EventLogger mEventLogger;
Przemyslaw Szczepaniak65327832013-05-31 13:12:52 +0100560 private final int mCallerUid;
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000561
Narayan Kamath492b7f02011-11-29 17:02:06 +0000562 public SynthesisSpeechItem(Object callerIdentity, int callerUid, int callerPid,
563 Bundle params, String text) {
564 super(callerIdentity, callerUid, callerPid, params);
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000565 mText = text;
Przemyslaw Szczepaniak65327832013-05-31 13:12:52 +0100566 mCallerUid = callerUid;
Narayan Kamathe22b69a2011-06-08 11:41:47 +0100567 mSynthesisRequest = new SynthesisRequest(mText, mParams);
Narayan Kamathe5b8c4d2011-08-22 15:37:47 +0100568 mDefaultLocale = getSettingsLocale();
Narayan Kamathe22b69a2011-06-08 11:41:47 +0100569 setRequestParams(mSynthesisRequest);
Narayan Kamath492b7f02011-11-29 17:02:06 +0000570 mEventLogger = new EventLogger(mSynthesisRequest, callerUid, callerPid,
571 mPackageName);
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000572 }
573
574 public String getText() {
575 return mText;
576 }
577
578 @Override
579 public boolean isValid() {
Narayan Kamath40f71f02011-11-23 16:42:53 +0000580 if (mText == null) {
Narayan Kamath9c3d7a82012-07-20 18:01:43 +0100581 Log.e(TAG, "null synthesis text");
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000582 return false;
583 }
Przemyslaw Szczepaniak2d940bc2012-11-19 12:22:59 +0000584 if (mText.length() >= TextToSpeech.getMaxSpeechInputLength()) {
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000585 Log.w(TAG, "Text too long: " + mText.length() + " chars");
586 return false;
587 }
588 return true;
589 }
590
591 @Override
592 protected int playImpl() {
Narayan Kamathe22b69a2011-06-08 11:41:47 +0100593 AbstractSynthesisCallback synthesisCallback;
Narayan Kamath6dabb632011-07-08 12:13:03 +0100594 mEventLogger.onRequestProcessingStart();
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000595 synchronized (this) {
Narayan Kamatha65c62a2011-09-06 17:04:48 +0100596 // stop() might have been called before we enter this
597 // synchronized block.
598 if (isStopped()) {
599 return TextToSpeech.ERROR;
600 }
Narayan Kamathe22b69a2011-06-08 11:41:47 +0100601 mSynthesisCallback = createSynthesisCallback();
602 synthesisCallback = mSynthesisCallback;
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000603 }
Narayan Kamathe22b69a2011-06-08 11:41:47 +0100604 TextToSpeechService.this.onSynthesizeText(mSynthesisRequest, synthesisCallback);
605 return synthesisCallback.isDone() ? TextToSpeech.SUCCESS : TextToSpeech.ERROR;
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000606 }
607
Narayan Kamathe22b69a2011-06-08 11:41:47 +0100608 protected AbstractSynthesisCallback createSynthesisCallback() {
609 return new PlaybackSynthesisCallback(getStreamType(), getVolume(), getPan(),
Narayan Kamath492b7f02011-11-29 17:02:06 +0000610 mAudioPlaybackHandler, this, getCallerIdentity(), mEventLogger);
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000611 }
612
613 private void setRequestParams(SynthesisRequest request) {
Narayan Kamathc3edf2a2011-06-15 12:35:06 +0100614 request.setLanguage(getLanguage(), getCountry(), getVariant());
615 request.setSpeechRate(getSpeechRate());
Przemyslaw Szczepaniak65327832013-05-31 13:12:52 +0100616 request.setCallerUid(mCallerUid);
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000617 request.setPitch(getPitch());
618 }
619
620 @Override
621 protected void stopImpl() {
Narayan Kamathe22b69a2011-06-08 11:41:47 +0100622 AbstractSynthesisCallback synthesisCallback;
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000623 synchronized (this) {
Narayan Kamathe22b69a2011-06-08 11:41:47 +0100624 synthesisCallback = mSynthesisCallback;
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000625 }
Narayan Kamatha65c62a2011-09-06 17:04:48 +0100626 if (synthesisCallback != null) {
627 // If the synthesis callback is null, it implies that we haven't
628 // entered the synchronized(this) block in playImpl which in
629 // turn implies that synthesis would not have started.
630 synthesisCallback.stop();
631 TextToSpeechService.this.onStop();
632 }
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000633 }
634
635 public String getLanguage() {
Narayan Kamathe5b8c4d2011-08-22 15:37:47 +0100636 return getStringParam(Engine.KEY_PARAM_LANGUAGE, mDefaultLocale[0]);
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000637 }
638
639 private boolean hasLanguage() {
640 return !TextUtils.isEmpty(getStringParam(Engine.KEY_PARAM_LANGUAGE, null));
641 }
642
643 private String getCountry() {
Narayan Kamathe5b8c4d2011-08-22 15:37:47 +0100644 if (!hasLanguage()) return mDefaultLocale[1];
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000645 return getStringParam(Engine.KEY_PARAM_COUNTRY, "");
646 }
647
648 private String getVariant() {
Narayan Kamathe5b8c4d2011-08-22 15:37:47 +0100649 if (!hasLanguage()) return mDefaultLocale[2];
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000650 return getStringParam(Engine.KEY_PARAM_VARIANT, "");
651 }
652
653 private int getSpeechRate() {
654 return getIntParam(Engine.KEY_PARAM_RATE, getDefaultSpeechRate());
655 }
656
657 private int getPitch() {
658 return getIntParam(Engine.KEY_PARAM_PITCH, Engine.DEFAULT_PITCH);
659 }
660 }
661
Przemyslaw Szczepaniakfcf671b2013-03-05 11:28:50 +0000662 private class SynthesisToFileOutputStreamSpeechItem extends SynthesisSpeechItem {
663 private final FileOutputStream mFileOutputStream;
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000664
Przemyslaw Szczepaniak73417862013-03-06 10:04:07 +0000665 public SynthesisToFileOutputStreamSpeechItem(Object callerIdentity, int callerUid,
Przemyslaw Szczepaniakfcf671b2013-03-05 11:28:50 +0000666 int callerPid, Bundle params, String text, FileOutputStream fileOutputStream) {
Narayan Kamath492b7f02011-11-29 17:02:06 +0000667 super(callerIdentity, callerUid, callerPid, params, text);
Przemyslaw Szczepaniakfcf671b2013-03-05 11:28:50 +0000668 mFileOutputStream = fileOutputStream;
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000669 }
670
671 @Override
Narayan Kamathe22b69a2011-06-08 11:41:47 +0100672 protected AbstractSynthesisCallback createSynthesisCallback() {
Przemyslaw Szczepaniakfcf671b2013-03-05 11:28:50 +0000673 return new FileSynthesisCallback(mFileOutputStream.getChannel());
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000674 }
675
Narayan Kamath8d1fc242011-06-03 18:11:54 +0100676 @Override
677 protected int playImpl() {
Narayan Kamath754c72e2011-11-09 14:22:32 +0000678 dispatchOnStart();
Narayan Kamath8d1fc242011-06-03 18:11:54 +0100679 int status = super.playImpl();
680 if (status == TextToSpeech.SUCCESS) {
Narayan Kamath754c72e2011-11-09 14:22:32 +0000681 dispatchOnDone();
682 } else {
683 dispatchOnError();
Narayan Kamath8d1fc242011-06-03 18:11:54 +0100684 }
Przemyslaw Szczepaniakfcf671b2013-03-05 11:28:50 +0000685 try {
686 mFileOutputStream.close();
687 } catch(IOException e) {
688 Log.w(TAG, "Failed to close output file", e);
689 }
Narayan Kamath8d1fc242011-06-03 18:11:54 +0100690 return status;
691 }
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000692 }
693
Przemyslaw Szczepaniak13896b72012-11-09 15:18:16 +0000694 private class AudioSpeechItem extends UtteranceSpeechItem {
Narayan Kamathaf802c62011-12-05 11:20:07 +0000695 private final AudioPlaybackQueueItem mItem;
Narayan Kamath492b7f02011-11-29 17:02:06 +0000696 public AudioSpeechItem(Object callerIdentity, int callerUid, int callerPid,
697 Bundle params, Uri uri) {
698 super(callerIdentity, callerUid, callerPid, params);
Narayan Kamathaf802c62011-12-05 11:20:07 +0000699 mItem = new AudioPlaybackQueueItem(this, getCallerIdentity(),
700 TextToSpeechService.this, uri, getStreamType());
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000701 }
702
703 @Override
704 public boolean isValid() {
705 return true;
706 }
707
708 @Override
709 protected int playImpl() {
Narayan Kamathaf802c62011-12-05 11:20:07 +0000710 mAudioPlaybackHandler.enqueue(mItem);
Narayan Kamath8d1fc242011-06-03 18:11:54 +0100711 return TextToSpeech.SUCCESS;
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000712 }
713
714 @Override
715 protected void stopImpl() {
Narayan Kamathbe4ad4a2011-07-15 13:01:09 +0100716 // Do nothing.
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000717 }
718 }
719
Przemyslaw Szczepaniak13896b72012-11-09 15:18:16 +0000720 private class SilenceSpeechItem extends UtteranceSpeechItem {
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000721 private final long mDuration;
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000722
Narayan Kamath492b7f02011-11-29 17:02:06 +0000723 public SilenceSpeechItem(Object callerIdentity, int callerUid, int callerPid,
724 Bundle params, long duration) {
725 super(callerIdentity, callerUid, callerPid, params);
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000726 mDuration = duration;
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000727 }
728
729 @Override
730 public boolean isValid() {
731 return true;
732 }
733
734 @Override
735 protected int playImpl() {
Narayan Kamath67ae6bc2011-11-30 14:51:00 +0000736 mAudioPlaybackHandler.enqueue(new SilencePlaybackQueueItem(
737 this, getCallerIdentity(), mDuration));
Narayan Kamath8d1fc242011-06-03 18:11:54 +0100738 return TextToSpeech.SUCCESS;
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000739 }
740
741 @Override
742 protected void stopImpl() {
Narayan Kamath67ae6bc2011-11-30 14:51:00 +0000743 // Do nothing, handled by AudioPlaybackHandler#stopForApp
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000744 }
745 }
746
Przemyslaw Szczepaniak13896b72012-11-09 15:18:16 +0000747 private class LoadLanguageItem extends SpeechItem {
748 private final String mLanguage;
749 private final String mCountry;
750 private final String mVariant;
751
752 public LoadLanguageItem(Object callerIdentity, int callerUid, int callerPid,
753 Bundle params, String language, String country, String variant) {
754 super(callerIdentity, callerUid, callerPid, params);
755 mLanguage = language;
756 mCountry = country;
757 mVariant = variant;
758 }
759
760 @Override
761 public boolean isValid() {
762 return true;
763 }
764
765 @Override
766 protected int playImpl() {
767 int result = TextToSpeechService.this.onLoadLanguage(mLanguage, mCountry, mVariant);
768 if (result == TextToSpeech.LANG_AVAILABLE ||
769 result == TextToSpeech.LANG_COUNTRY_AVAILABLE ||
770 result == TextToSpeech.LANG_COUNTRY_VAR_AVAILABLE) {
771 return TextToSpeech.SUCCESS;
772 }
773 return TextToSpeech.ERROR;
774 }
775
776 @Override
777 protected void stopImpl() {
778 // No-op
779 }
780 }
781
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000782 @Override
783 public IBinder onBind(Intent intent) {
784 if (TextToSpeech.Engine.INTENT_ACTION_TTS_SERVICE.equals(intent.getAction())) {
785 return mBinder;
786 }
787 return null;
788 }
789
790 /**
791 * Binder returned from {@code #onBind(Intent)}. The methods in this class can be
792 * called called from several different threads.
793 */
Narayan Kamathabc63fb2011-06-10 11:36:57 +0100794 // NOTE: All calls that are passed in a calling app are interned so that
795 // they can be used as message objects (which are tested for equality using ==).
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000796 private final ITextToSpeechService.Stub mBinder = new ITextToSpeechService.Stub() {
Narayan Kamath492b7f02011-11-29 17:02:06 +0000797 @Override
798 public int speak(IBinder caller, String text, int queueMode, Bundle params) {
799 if (!checkNonNull(caller, text, params)) {
Narayan Kamathabc63fb2011-06-10 11:36:57 +0100800 return TextToSpeech.ERROR;
801 }
802
Narayan Kamath492b7f02011-11-29 17:02:06 +0000803 SpeechItem item = new SynthesisSpeechItem(caller,
804 Binder.getCallingUid(), Binder.getCallingPid(), params, text);
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000805 return mSynthHandler.enqueueSpeechItem(queueMode, item);
806 }
807
Narayan Kamath492b7f02011-11-29 17:02:06 +0000808 @Override
Przemyslaw Szczepaniak5acb33a2013-02-08 16:36:25 +0000809 public int synthesizeToFileDescriptor(IBinder caller, String text, ParcelFileDescriptor
810 fileDescriptor, Bundle params) {
811 if (!checkNonNull(caller, text, fileDescriptor, params)) {
Narayan Kamathabc63fb2011-06-10 11:36:57 +0100812 return TextToSpeech.ERROR;
813 }
814
Przemyslaw Szczepaniak24943bf2013-03-13 15:45:28 +0000815 // In test env, ParcelFileDescriptor instance may be EXACTLY the same
816 // one that is used by client. And it will be closed by a client, thus
817 // preventing us from writing anything to it.
818 final ParcelFileDescriptor sameFileDescriptor = ParcelFileDescriptor.adoptFd(
819 fileDescriptor.detachFd());
820
Przemyslaw Szczepaniakfcf671b2013-03-05 11:28:50 +0000821 SpeechItem item = new SynthesisToFileOutputStreamSpeechItem(caller,
822 Binder.getCallingUid(), Binder.getCallingPid(), params, text,
Przemyslaw Szczepaniak24943bf2013-03-13 15:45:28 +0000823 new ParcelFileDescriptor.AutoCloseOutputStream(sameFileDescriptor));
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000824 return mSynthHandler.enqueueSpeechItem(TextToSpeech.QUEUE_ADD, item);
825 }
826
Narayan Kamath492b7f02011-11-29 17:02:06 +0000827 @Override
828 public int playAudio(IBinder caller, Uri audioUri, int queueMode, Bundle params) {
829 if (!checkNonNull(caller, audioUri, params)) {
Narayan Kamathabc63fb2011-06-10 11:36:57 +0100830 return TextToSpeech.ERROR;
831 }
832
Narayan Kamath492b7f02011-11-29 17:02:06 +0000833 SpeechItem item = new AudioSpeechItem(caller,
834 Binder.getCallingUid(), Binder.getCallingPid(), params, audioUri);
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000835 return mSynthHandler.enqueueSpeechItem(queueMode, item);
836 }
837
Narayan Kamath492b7f02011-11-29 17:02:06 +0000838 @Override
839 public int playSilence(IBinder caller, long duration, int queueMode, Bundle params) {
840 if (!checkNonNull(caller, params)) {
Narayan Kamathabc63fb2011-06-10 11:36:57 +0100841 return TextToSpeech.ERROR;
842 }
843
Narayan Kamath492b7f02011-11-29 17:02:06 +0000844 SpeechItem item = new SilenceSpeechItem(caller,
845 Binder.getCallingUid(), Binder.getCallingPid(), params, duration);
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000846 return mSynthHandler.enqueueSpeechItem(queueMode, item);
847 }
848
Narayan Kamath492b7f02011-11-29 17:02:06 +0000849 @Override
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000850 public boolean isSpeaking() {
Narayan Kamathc34f76f2011-07-15 11:13:10 +0100851 return mSynthHandler.isSpeaking() || mAudioPlaybackHandler.isSpeaking();
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000852 }
853
Narayan Kamath492b7f02011-11-29 17:02:06 +0000854 @Override
855 public int stop(IBinder caller) {
856 if (!checkNonNull(caller)) {
Narayan Kamathabc63fb2011-06-10 11:36:57 +0100857 return TextToSpeech.ERROR;
858 }
859
Narayan Kamath492b7f02011-11-29 17:02:06 +0000860 return mSynthHandler.stopForApp(caller);
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000861 }
862
Narayan Kamath492b7f02011-11-29 17:02:06 +0000863 @Override
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000864 public String[] getLanguage() {
865 return onGetLanguage();
866 }
867
Przemyslaw Szczepaniakb4653372012-12-04 14:57:58 +0000868 @Override
869 public String[] getClientDefaultLanguage() {
870 return getSettingsLocale();
871 }
872
Narayan Kamath7a3af862011-06-02 17:28:57 +0100873 /*
874 * If defaults are enforced, then no language is "available" except
875 * perhaps the default language selected by the user.
876 */
Narayan Kamath492b7f02011-11-29 17:02:06 +0000877 @Override
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000878 public int isLanguageAvailable(String lang, String country, String variant) {
Narayan Kamathabc63fb2011-06-10 11:36:57 +0100879 if (!checkNonNull(lang)) {
880 return TextToSpeech.ERROR;
881 }
882
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000883 return onIsLanguageAvailable(lang, country, variant);
884 }
885
Narayan Kamath492b7f02011-11-29 17:02:06 +0000886 @Override
Narayan Kamath748af662011-10-31 14:20:01 +0000887 public String[] getFeaturesForLanguage(String lang, String country, String variant) {
888 Set<String> features = onGetFeaturesForLanguage(lang, country, variant);
Narayan Kamath6c07a202011-11-07 14:21:39 +0000889 String[] featuresArray = null;
890 if (features != null) {
891 featuresArray = new String[features.size()];
892 features.toArray(featuresArray);
893 } else {
894 featuresArray = new String[0];
895 }
Narayan Kamath748af662011-10-31 14:20:01 +0000896 return featuresArray;
897 }
898
Narayan Kamath7a3af862011-06-02 17:28:57 +0100899 /*
900 * There is no point loading a non default language if defaults
901 * are enforced.
902 */
Narayan Kamath492b7f02011-11-29 17:02:06 +0000903 @Override
Przemyslaw Szczepaniak13896b72012-11-09 15:18:16 +0000904 public int loadLanguage(IBinder caller, String lang, String country, String variant) {
Narayan Kamathabc63fb2011-06-10 11:36:57 +0100905 if (!checkNonNull(lang)) {
906 return TextToSpeech.ERROR;
907 }
Przemyslaw Szczepaniak13896b72012-11-09 15:18:16 +0000908 int retVal = onIsLanguageAvailable(lang, country, variant);
Narayan Kamathabc63fb2011-06-10 11:36:57 +0100909
Przemyslaw Szczepaniak13896b72012-11-09 15:18:16 +0000910 if (retVal == TextToSpeech.LANG_AVAILABLE ||
911 retVal == TextToSpeech.LANG_COUNTRY_AVAILABLE ||
912 retVal == TextToSpeech.LANG_COUNTRY_VAR_AVAILABLE) {
913
914 SpeechItem item = new LoadLanguageItem(caller, Binder.getCallingUid(),
915 Binder.getCallingPid(), null, lang, country, variant);
916
917 if (mSynthHandler.enqueueSpeechItem(TextToSpeech.QUEUE_ADD, item) !=
918 TextToSpeech.SUCCESS) {
919 return TextToSpeech.ERROR;
920 }
921 }
922 return retVal;
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000923 }
924
Narayan Kamath492b7f02011-11-29 17:02:06 +0000925 @Override
926 public void setCallback(IBinder caller, ITextToSpeechCallback cb) {
Narayan Kamathabc63fb2011-06-10 11:36:57 +0100927 // Note that passing in a null callback is a valid use case.
Narayan Kamath492b7f02011-11-29 17:02:06 +0000928 if (!checkNonNull(caller)) {
Narayan Kamathabc63fb2011-06-10 11:36:57 +0100929 return;
930 }
931
Narayan Kamath492b7f02011-11-29 17:02:06 +0000932 mCallbacks.setCallback(caller, cb);
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000933 }
Narayan Kamath7a3af862011-06-02 17:28:57 +0100934
Narayan Kamathabc63fb2011-06-10 11:36:57 +0100935 private String intern(String in) {
936 // The input parameter will be non null.
937 return in.intern();
938 }
939
940 private boolean checkNonNull(Object... args) {
941 for (Object o : args) {
942 if (o == null) return false;
943 }
944 return true;
945 }
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000946 };
947
948 private class CallbackMap extends RemoteCallbackList<ITextToSpeechCallback> {
Narayan Kamath492b7f02011-11-29 17:02:06 +0000949 private final HashMap<IBinder, ITextToSpeechCallback> mCallerToCallback
950 = new HashMap<IBinder, ITextToSpeechCallback>();
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000951
Narayan Kamath492b7f02011-11-29 17:02:06 +0000952 public void setCallback(IBinder caller, ITextToSpeechCallback cb) {
953 synchronized (mCallerToCallback) {
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000954 ITextToSpeechCallback old;
955 if (cb != null) {
Narayan Kamath492b7f02011-11-29 17:02:06 +0000956 register(cb, caller);
957 old = mCallerToCallback.put(caller, cb);
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000958 } else {
Narayan Kamath492b7f02011-11-29 17:02:06 +0000959 old = mCallerToCallback.remove(caller);
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000960 }
961 if (old != null && old != cb) {
962 unregister(old);
963 }
964 }
965 }
966
Narayan Kamath492b7f02011-11-29 17:02:06 +0000967 public void dispatchOnDone(Object callerIdentity, String utteranceId) {
968 ITextToSpeechCallback cb = getCallbackFor(callerIdentity);
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000969 if (cb == null) return;
970 try {
Narayan Kamath754c72e2011-11-09 14:22:32 +0000971 cb.onDone(utteranceId);
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000972 } catch (RemoteException e) {
Narayan Kamath754c72e2011-11-09 14:22:32 +0000973 Log.e(TAG, "Callback onDone failed: " + e);
974 }
975 }
976
Narayan Kamath492b7f02011-11-29 17:02:06 +0000977 public void dispatchOnStart(Object callerIdentity, String utteranceId) {
978 ITextToSpeechCallback cb = getCallbackFor(callerIdentity);
Narayan Kamath754c72e2011-11-09 14:22:32 +0000979 if (cb == null) return;
980 try {
981 cb.onStart(utteranceId);
982 } catch (RemoteException e) {
983 Log.e(TAG, "Callback onStart failed: " + e);
984 }
985
986 }
987
Narayan Kamath492b7f02011-11-29 17:02:06 +0000988 public void dispatchOnError(Object callerIdentity, String utteranceId) {
989 ITextToSpeechCallback cb = getCallbackFor(callerIdentity);
Narayan Kamath754c72e2011-11-09 14:22:32 +0000990 if (cb == null) return;
991 try {
992 cb.onError(utteranceId);
993 } catch (RemoteException e) {
994 Log.e(TAG, "Callback onError failed: " + e);
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000995 }
996 }
997
998 @Override
999 public void onCallbackDied(ITextToSpeechCallback callback, Object cookie) {
Narayan Kamath492b7f02011-11-29 17:02:06 +00001000 IBinder caller = (IBinder) cookie;
1001 synchronized (mCallerToCallback) {
1002 mCallerToCallback.remove(caller);
Bjorn Bringert50e657b2011-03-08 16:00:40 +00001003 }
Narayan Kamath492b7f02011-11-29 17:02:06 +00001004 mSynthHandler.stopForApp(caller);
Bjorn Bringert50e657b2011-03-08 16:00:40 +00001005 }
1006
1007 @Override
1008 public void kill() {
Narayan Kamath492b7f02011-11-29 17:02:06 +00001009 synchronized (mCallerToCallback) {
1010 mCallerToCallback.clear();
Bjorn Bringert50e657b2011-03-08 16:00:40 +00001011 super.kill();
1012 }
1013 }
1014
Narayan Kamath492b7f02011-11-29 17:02:06 +00001015 private ITextToSpeechCallback getCallbackFor(Object caller) {
Narayan Kamath754c72e2011-11-09 14:22:32 +00001016 ITextToSpeechCallback cb;
Narayan Kamath492b7f02011-11-29 17:02:06 +00001017 IBinder asBinder = (IBinder) caller;
1018 synchronized (mCallerToCallback) {
1019 cb = mCallerToCallback.get(asBinder);
Narayan Kamath754c72e2011-11-09 14:22:32 +00001020 }
1021
1022 return cb;
1023 }
1024
Bjorn Bringert50e657b2011-03-08 16:00:40 +00001025 }
1026
1027}