blob: 703dcff1688cb8733ad5e257edbadde43b37bb52 [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;
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000560
Narayan Kamath492b7f02011-11-29 17:02:06 +0000561 public SynthesisSpeechItem(Object callerIdentity, int callerUid, int callerPid,
562 Bundle params, String text) {
563 super(callerIdentity, callerUid, callerPid, params);
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000564 mText = text;
Narayan Kamathe22b69a2011-06-08 11:41:47 +0100565 mSynthesisRequest = new SynthesisRequest(mText, mParams);
Narayan Kamathe5b8c4d2011-08-22 15:37:47 +0100566 mDefaultLocale = getSettingsLocale();
Narayan Kamathe22b69a2011-06-08 11:41:47 +0100567 setRequestParams(mSynthesisRequest);
Narayan Kamath492b7f02011-11-29 17:02:06 +0000568 mEventLogger = new EventLogger(mSynthesisRequest, callerUid, callerPid,
569 mPackageName);
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000570 }
571
572 public String getText() {
573 return mText;
574 }
575
576 @Override
577 public boolean isValid() {
Narayan Kamath40f71f02011-11-23 16:42:53 +0000578 if (mText == null) {
Narayan Kamath9c3d7a82012-07-20 18:01:43 +0100579 Log.e(TAG, "null synthesis text");
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000580 return false;
581 }
Przemyslaw Szczepaniak2d940bc2012-11-19 12:22:59 +0000582 if (mText.length() >= TextToSpeech.getMaxSpeechInputLength()) {
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000583 Log.w(TAG, "Text too long: " + mText.length() + " chars");
584 return false;
585 }
586 return true;
587 }
588
589 @Override
590 protected int playImpl() {
Narayan Kamathe22b69a2011-06-08 11:41:47 +0100591 AbstractSynthesisCallback synthesisCallback;
Narayan Kamath6dabb632011-07-08 12:13:03 +0100592 mEventLogger.onRequestProcessingStart();
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000593 synchronized (this) {
Narayan Kamatha65c62a2011-09-06 17:04:48 +0100594 // stop() might have been called before we enter this
595 // synchronized block.
596 if (isStopped()) {
597 return TextToSpeech.ERROR;
598 }
Narayan Kamathe22b69a2011-06-08 11:41:47 +0100599 mSynthesisCallback = createSynthesisCallback();
600 synthesisCallback = mSynthesisCallback;
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000601 }
Narayan Kamathe22b69a2011-06-08 11:41:47 +0100602 TextToSpeechService.this.onSynthesizeText(mSynthesisRequest, synthesisCallback);
603 return synthesisCallback.isDone() ? TextToSpeech.SUCCESS : TextToSpeech.ERROR;
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000604 }
605
Narayan Kamathe22b69a2011-06-08 11:41:47 +0100606 protected AbstractSynthesisCallback createSynthesisCallback() {
607 return new PlaybackSynthesisCallback(getStreamType(), getVolume(), getPan(),
Narayan Kamath492b7f02011-11-29 17:02:06 +0000608 mAudioPlaybackHandler, this, getCallerIdentity(), mEventLogger);
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000609 }
610
611 private void setRequestParams(SynthesisRequest request) {
Narayan Kamathc3edf2a2011-06-15 12:35:06 +0100612 request.setLanguage(getLanguage(), getCountry(), getVariant());
613 request.setSpeechRate(getSpeechRate());
614
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000615 request.setPitch(getPitch());
616 }
617
618 @Override
619 protected void stopImpl() {
Narayan Kamathe22b69a2011-06-08 11:41:47 +0100620 AbstractSynthesisCallback synthesisCallback;
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000621 synchronized (this) {
Narayan Kamathe22b69a2011-06-08 11:41:47 +0100622 synthesisCallback = mSynthesisCallback;
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000623 }
Narayan Kamatha65c62a2011-09-06 17:04:48 +0100624 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 }
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000631 }
632
633 public String getLanguage() {
Narayan Kamathe5b8c4d2011-08-22 15:37:47 +0100634 return getStringParam(Engine.KEY_PARAM_LANGUAGE, mDefaultLocale[0]);
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000635 }
636
637 private boolean hasLanguage() {
638 return !TextUtils.isEmpty(getStringParam(Engine.KEY_PARAM_LANGUAGE, null));
639 }
640
641 private String getCountry() {
Narayan Kamathe5b8c4d2011-08-22 15:37:47 +0100642 if (!hasLanguage()) return mDefaultLocale[1];
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000643 return getStringParam(Engine.KEY_PARAM_COUNTRY, "");
644 }
645
646 private String getVariant() {
Narayan Kamathe5b8c4d2011-08-22 15:37:47 +0100647 if (!hasLanguage()) return mDefaultLocale[2];
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000648 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
Przemyslaw Szczepaniakfcf671b2013-03-05 11:28:50 +0000660 private class SynthesisToFileOutputStreamSpeechItem extends SynthesisSpeechItem {
661 private final FileOutputStream mFileOutputStream;
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000662
Przemyslaw Szczepaniak73417862013-03-06 10:04:07 +0000663 public SynthesisToFileOutputStreamSpeechItem(Object callerIdentity, int callerUid,
Przemyslaw Szczepaniakfcf671b2013-03-05 11:28:50 +0000664 int callerPid, Bundle params, String text, FileOutputStream fileOutputStream) {
Narayan Kamath492b7f02011-11-29 17:02:06 +0000665 super(callerIdentity, callerUid, callerPid, params, text);
Przemyslaw Szczepaniakfcf671b2013-03-05 11:28:50 +0000666 mFileOutputStream = fileOutputStream;
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000667 }
668
669 @Override
Narayan Kamathe22b69a2011-06-08 11:41:47 +0100670 protected AbstractSynthesisCallback createSynthesisCallback() {
Przemyslaw Szczepaniakfcf671b2013-03-05 11:28:50 +0000671 return new FileSynthesisCallback(mFileOutputStream.getChannel());
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000672 }
673
Narayan Kamath8d1fc242011-06-03 18:11:54 +0100674 @Override
675 protected int playImpl() {
Narayan Kamath754c72e2011-11-09 14:22:32 +0000676 dispatchOnStart();
Narayan Kamath8d1fc242011-06-03 18:11:54 +0100677 int status = super.playImpl();
678 if (status == TextToSpeech.SUCCESS) {
Narayan Kamath754c72e2011-11-09 14:22:32 +0000679 dispatchOnDone();
680 } else {
681 dispatchOnError();
Narayan Kamath8d1fc242011-06-03 18:11:54 +0100682 }
Przemyslaw Szczepaniakfcf671b2013-03-05 11:28:50 +0000683 try {
684 mFileOutputStream.close();
685 } catch(IOException e) {
686 Log.w(TAG, "Failed to close output file", e);
687 }
Narayan Kamath8d1fc242011-06-03 18:11:54 +0100688 return status;
689 }
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000690 }
691
Przemyslaw Szczepaniak13896b72012-11-09 15:18:16 +0000692 private class AudioSpeechItem extends UtteranceSpeechItem {
Narayan Kamathaf802c62011-12-05 11:20:07 +0000693 private final AudioPlaybackQueueItem mItem;
Narayan Kamath492b7f02011-11-29 17:02:06 +0000694 public AudioSpeechItem(Object callerIdentity, int callerUid, int callerPid,
695 Bundle params, Uri uri) {
696 super(callerIdentity, callerUid, callerPid, params);
Narayan Kamathaf802c62011-12-05 11:20:07 +0000697 mItem = new AudioPlaybackQueueItem(this, getCallerIdentity(),
698 TextToSpeechService.this, uri, getStreamType());
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000699 }
700
701 @Override
702 public boolean isValid() {
703 return true;
704 }
705
706 @Override
707 protected int playImpl() {
Narayan Kamathaf802c62011-12-05 11:20:07 +0000708 mAudioPlaybackHandler.enqueue(mItem);
Narayan Kamath8d1fc242011-06-03 18:11:54 +0100709 return TextToSpeech.SUCCESS;
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000710 }
711
712 @Override
713 protected void stopImpl() {
Narayan Kamathbe4ad4a2011-07-15 13:01:09 +0100714 // Do nothing.
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000715 }
716 }
717
Przemyslaw Szczepaniak13896b72012-11-09 15:18:16 +0000718 private class SilenceSpeechItem extends UtteranceSpeechItem {
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000719 private final long mDuration;
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000720
Narayan Kamath492b7f02011-11-29 17:02:06 +0000721 public SilenceSpeechItem(Object callerIdentity, int callerUid, int callerPid,
722 Bundle params, long duration) {
723 super(callerIdentity, callerUid, callerPid, params);
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000724 mDuration = duration;
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000725 }
726
727 @Override
728 public boolean isValid() {
729 return true;
730 }
731
732 @Override
733 protected int playImpl() {
Narayan Kamath67ae6bc2011-11-30 14:51:00 +0000734 mAudioPlaybackHandler.enqueue(new SilencePlaybackQueueItem(
735 this, getCallerIdentity(), mDuration));
Narayan Kamath8d1fc242011-06-03 18:11:54 +0100736 return TextToSpeech.SUCCESS;
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000737 }
738
739 @Override
740 protected void stopImpl() {
Narayan Kamath67ae6bc2011-11-30 14:51:00 +0000741 // Do nothing, handled by AudioPlaybackHandler#stopForApp
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000742 }
743 }
744
Przemyslaw Szczepaniak13896b72012-11-09 15:18:16 +0000745 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
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000780 @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 */
Narayan Kamathabc63fb2011-06-10 11:36:57 +0100792 // 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 ==).
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000794 private final ITextToSpeechService.Stub mBinder = new ITextToSpeechService.Stub() {
Narayan Kamath492b7f02011-11-29 17:02:06 +0000795 @Override
796 public int speak(IBinder caller, String text, int queueMode, Bundle params) {
797 if (!checkNonNull(caller, text, params)) {
Narayan Kamathabc63fb2011-06-10 11:36:57 +0100798 return TextToSpeech.ERROR;
799 }
800
Narayan Kamath492b7f02011-11-29 17:02:06 +0000801 SpeechItem item = new SynthesisSpeechItem(caller,
802 Binder.getCallingUid(), Binder.getCallingPid(), params, text);
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000803 return mSynthHandler.enqueueSpeechItem(queueMode, item);
804 }
805
Narayan Kamath492b7f02011-11-29 17:02:06 +0000806 @Override
Przemyslaw Szczepaniak5acb33a2013-02-08 16:36:25 +0000807 public int synthesizeToFileDescriptor(IBinder caller, String text, ParcelFileDescriptor
808 fileDescriptor, Bundle params) {
809 if (!checkNonNull(caller, text, fileDescriptor, params)) {
Narayan Kamathabc63fb2011-06-10 11:36:57 +0100810 return TextToSpeech.ERROR;
811 }
812
Przemyslaw Szczepaniak24943bf2013-03-13 15:45:28 +0000813 // 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
Przemyslaw Szczepaniakfcf671b2013-03-05 11:28:50 +0000819 SpeechItem item = new SynthesisToFileOutputStreamSpeechItem(caller,
820 Binder.getCallingUid(), Binder.getCallingPid(), params, text,
Przemyslaw Szczepaniak24943bf2013-03-13 15:45:28 +0000821 new ParcelFileDescriptor.AutoCloseOutputStream(sameFileDescriptor));
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000822 return mSynthHandler.enqueueSpeechItem(TextToSpeech.QUEUE_ADD, item);
823 }
824
Narayan Kamath492b7f02011-11-29 17:02:06 +0000825 @Override
826 public int playAudio(IBinder caller, Uri audioUri, int queueMode, Bundle params) {
827 if (!checkNonNull(caller, audioUri, params)) {
Narayan Kamathabc63fb2011-06-10 11:36:57 +0100828 return TextToSpeech.ERROR;
829 }
830
Narayan Kamath492b7f02011-11-29 17:02:06 +0000831 SpeechItem item = new AudioSpeechItem(caller,
832 Binder.getCallingUid(), Binder.getCallingPid(), params, audioUri);
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000833 return mSynthHandler.enqueueSpeechItem(queueMode, item);
834 }
835
Narayan Kamath492b7f02011-11-29 17:02:06 +0000836 @Override
837 public int playSilence(IBinder caller, long duration, int queueMode, Bundle params) {
838 if (!checkNonNull(caller, params)) {
Narayan Kamathabc63fb2011-06-10 11:36:57 +0100839 return TextToSpeech.ERROR;
840 }
841
Narayan Kamath492b7f02011-11-29 17:02:06 +0000842 SpeechItem item = new SilenceSpeechItem(caller,
843 Binder.getCallingUid(), Binder.getCallingPid(), params, duration);
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000844 return mSynthHandler.enqueueSpeechItem(queueMode, item);
845 }
846
Narayan Kamath492b7f02011-11-29 17:02:06 +0000847 @Override
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000848 public boolean isSpeaking() {
Narayan Kamathc34f76f2011-07-15 11:13:10 +0100849 return mSynthHandler.isSpeaking() || mAudioPlaybackHandler.isSpeaking();
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000850 }
851
Narayan Kamath492b7f02011-11-29 17:02:06 +0000852 @Override
853 public int stop(IBinder caller) {
854 if (!checkNonNull(caller)) {
Narayan Kamathabc63fb2011-06-10 11:36:57 +0100855 return TextToSpeech.ERROR;
856 }
857
Narayan Kamath492b7f02011-11-29 17:02:06 +0000858 return mSynthHandler.stopForApp(caller);
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000859 }
860
Narayan Kamath492b7f02011-11-29 17:02:06 +0000861 @Override
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000862 public String[] getLanguage() {
863 return onGetLanguage();
864 }
865
Przemyslaw Szczepaniakb4653372012-12-04 14:57:58 +0000866 @Override
867 public String[] getClientDefaultLanguage() {
868 return getSettingsLocale();
869 }
870
Narayan Kamath7a3af862011-06-02 17:28:57 +0100871 /*
872 * If defaults are enforced, then no language is "available" except
873 * perhaps the default language selected by the user.
874 */
Narayan Kamath492b7f02011-11-29 17:02:06 +0000875 @Override
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000876 public int isLanguageAvailable(String lang, String country, String variant) {
Narayan Kamathabc63fb2011-06-10 11:36:57 +0100877 if (!checkNonNull(lang)) {
878 return TextToSpeech.ERROR;
879 }
880
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000881 return onIsLanguageAvailable(lang, country, variant);
882 }
883
Narayan Kamath492b7f02011-11-29 17:02:06 +0000884 @Override
Narayan Kamath748af662011-10-31 14:20:01 +0000885 public String[] getFeaturesForLanguage(String lang, String country, String variant) {
886 Set<String> features = onGetFeaturesForLanguage(lang, country, variant);
Narayan Kamath6c07a202011-11-07 14:21:39 +0000887 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 }
Narayan Kamath748af662011-10-31 14:20:01 +0000894 return featuresArray;
895 }
896
Narayan Kamath7a3af862011-06-02 17:28:57 +0100897 /*
898 * There is no point loading a non default language if defaults
899 * are enforced.
900 */
Narayan Kamath492b7f02011-11-29 17:02:06 +0000901 @Override
Przemyslaw Szczepaniak13896b72012-11-09 15:18:16 +0000902 public int loadLanguage(IBinder caller, String lang, String country, String variant) {
Narayan Kamathabc63fb2011-06-10 11:36:57 +0100903 if (!checkNonNull(lang)) {
904 return TextToSpeech.ERROR;
905 }
Przemyslaw Szczepaniak13896b72012-11-09 15:18:16 +0000906 int retVal = onIsLanguageAvailable(lang, country, variant);
Narayan Kamathabc63fb2011-06-10 11:36:57 +0100907
Przemyslaw Szczepaniak13896b72012-11-09 15:18:16 +0000908 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;
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000921 }
922
Narayan Kamath492b7f02011-11-29 17:02:06 +0000923 @Override
924 public void setCallback(IBinder caller, ITextToSpeechCallback cb) {
Narayan Kamathabc63fb2011-06-10 11:36:57 +0100925 // Note that passing in a null callback is a valid use case.
Narayan Kamath492b7f02011-11-29 17:02:06 +0000926 if (!checkNonNull(caller)) {
Narayan Kamathabc63fb2011-06-10 11:36:57 +0100927 return;
928 }
929
Narayan Kamath492b7f02011-11-29 17:02:06 +0000930 mCallbacks.setCallback(caller, cb);
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000931 }
Narayan Kamath7a3af862011-06-02 17:28:57 +0100932
Narayan Kamathabc63fb2011-06-10 11:36:57 +0100933 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 }
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000944 };
945
946 private class CallbackMap extends RemoteCallbackList<ITextToSpeechCallback> {
Narayan Kamath492b7f02011-11-29 17:02:06 +0000947 private final HashMap<IBinder, ITextToSpeechCallback> mCallerToCallback
948 = new HashMap<IBinder, ITextToSpeechCallback>();
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000949
Narayan Kamath492b7f02011-11-29 17:02:06 +0000950 public void setCallback(IBinder caller, ITextToSpeechCallback cb) {
951 synchronized (mCallerToCallback) {
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000952 ITextToSpeechCallback old;
953 if (cb != null) {
Narayan Kamath492b7f02011-11-29 17:02:06 +0000954 register(cb, caller);
955 old = mCallerToCallback.put(caller, cb);
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000956 } else {
Narayan Kamath492b7f02011-11-29 17:02:06 +0000957 old = mCallerToCallback.remove(caller);
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000958 }
959 if (old != null && old != cb) {
960 unregister(old);
961 }
962 }
963 }
964
Narayan Kamath492b7f02011-11-29 17:02:06 +0000965 public void dispatchOnDone(Object callerIdentity, String utteranceId) {
966 ITextToSpeechCallback cb = getCallbackFor(callerIdentity);
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000967 if (cb == null) return;
968 try {
Narayan Kamath754c72e2011-11-09 14:22:32 +0000969 cb.onDone(utteranceId);
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000970 } catch (RemoteException e) {
Narayan Kamath754c72e2011-11-09 14:22:32 +0000971 Log.e(TAG, "Callback onDone failed: " + e);
972 }
973 }
974
Narayan Kamath492b7f02011-11-29 17:02:06 +0000975 public void dispatchOnStart(Object callerIdentity, String utteranceId) {
976 ITextToSpeechCallback cb = getCallbackFor(callerIdentity);
Narayan Kamath754c72e2011-11-09 14:22:32 +0000977 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
Narayan Kamath492b7f02011-11-29 17:02:06 +0000986 public void dispatchOnError(Object callerIdentity, String utteranceId) {
987 ITextToSpeechCallback cb = getCallbackFor(callerIdentity);
Narayan Kamath754c72e2011-11-09 14:22:32 +0000988 if (cb == null) return;
989 try {
990 cb.onError(utteranceId);
991 } catch (RemoteException e) {
992 Log.e(TAG, "Callback onError failed: " + e);
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000993 }
994 }
995
996 @Override
997 public void onCallbackDied(ITextToSpeechCallback callback, Object cookie) {
Narayan Kamath492b7f02011-11-29 17:02:06 +0000998 IBinder caller = (IBinder) cookie;
999 synchronized (mCallerToCallback) {
1000 mCallerToCallback.remove(caller);
Bjorn Bringert50e657b2011-03-08 16:00:40 +00001001 }
Narayan Kamath492b7f02011-11-29 17:02:06 +00001002 mSynthHandler.stopForApp(caller);
Bjorn Bringert50e657b2011-03-08 16:00:40 +00001003 }
1004
1005 @Override
1006 public void kill() {
Narayan Kamath492b7f02011-11-29 17:02:06 +00001007 synchronized (mCallerToCallback) {
1008 mCallerToCallback.clear();
Bjorn Bringert50e657b2011-03-08 16:00:40 +00001009 super.kill();
1010 }
1011 }
1012
Narayan Kamath492b7f02011-11-29 17:02:06 +00001013 private ITextToSpeechCallback getCallbackFor(Object caller) {
Narayan Kamath754c72e2011-11-09 14:22:32 +00001014 ITextToSpeechCallback cb;
Narayan Kamath492b7f02011-11-29 17:02:06 +00001015 IBinder asBinder = (IBinder) caller;
1016 synchronized (mCallerToCallback) {
1017 cb = mCallerToCallback.get(asBinder);
Narayan Kamath754c72e2011-11-09 14:22:32 +00001018 }
1019
1020 return cb;
1021 }
1022
Bjorn Bringert50e657b2011-03-08 16:00:40 +00001023 }
1024
1025}