blob: c645f4057335a90ad011d950441eaa12b2d2ff30 [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
Niels Egbertsc99ba1c2015-11-20 11:44:25 +000018import android.annotation.NonNull;
Bjorn Bringert50e657b2011-03-08 16:00:40 +000019import android.app.Service;
20import android.content.Intent;
Przemyslaw Szczepaniak672695e2014-07-14 17:10:42 +010021import android.media.AudioAttributes;
Glenn Kasten33b84042016-03-08 12:02:55 -080022import android.media.AudioManager;
Bjorn Bringert50e657b2011-03-08 16:00:40 +000023import android.net.Uri;
Narayan Kamath492b7f02011-11-29 17:02:06 +000024import android.os.Binder;
Bjorn Bringert50e657b2011-03-08 16:00:40 +000025import android.os.Bundle;
Bjorn Bringert50e657b2011-03-08 16:00:40 +000026import android.os.Handler;
27import android.os.HandlerThread;
28import android.os.IBinder;
29import android.os.Looper;
30import android.os.Message;
31import android.os.MessageQueue;
Przemyslaw Szczepaniak5acb33a2013-02-08 16:36:25 +000032import android.os.ParcelFileDescriptor;
Bjorn Bringert50e657b2011-03-08 16:00:40 +000033import android.os.RemoteCallbackList;
34import android.os.RemoteException;
35import android.provider.Settings;
36import android.speech.tts.TextToSpeech.Engine;
37import android.text.TextUtils;
38import android.util.Log;
39
Przemyslaw Szczepaniak5acb33a2013-02-08 16:36:25 +000040import java.io.FileOutputStream;
Przemyslaw Szczepaniakfcf671b2013-03-05 11:28:50 +000041import java.io.IOException;
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +010042import java.util.ArrayList;
Bjorn Bringert50e657b2011-03-08 16:00:40 +000043import java.util.HashMap;
Niels Egberts54545f72015-08-05 15:41:18 +010044import java.util.HashSet;
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +010045import java.util.List;
Bjorn Bringert50e657b2011-03-08 16:00:40 +000046import java.util.Locale;
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +010047import java.util.MissingResourceException;
Narayan Kamath748af662011-10-31 14:20:01 +000048import java.util.Set;
Bjorn Bringert50e657b2011-03-08 16:00:40 +000049
50
51/**
Narayan Kamathe22b69a2011-06-08 11:41:47 +010052 * Abstract base class for TTS engine implementations. The following methods
Przemyslaw Szczepaniakad6df742014-07-01 17:04:25 +010053 * need to be implemented:
Narayan Kamathe22b69a2011-06-08 11:41:47 +010054 * <ul>
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +010055 * <li>{@link #onIsLanguageAvailable}</li>
56 * <li>{@link #onLoadLanguage}</li>
57 * <li>{@link #onGetLanguage}</li>
58 * <li>{@link #onSynthesizeText}</li>
59 * <li>{@link #onStop}</li>
Narayan Kamathe22b69a2011-06-08 11:41:47 +010060 * </ul>
Narayan Kamathe22b69a2011-06-08 11:41:47 +010061 * The first three deal primarily with language management, and are used to
62 * query the engine for it's support for a given language and indicate to it
63 * that requests in a given language are imminent.
64 *
65 * {@link #onSynthesizeText} is central to the engine implementation. The
66 * implementation should synthesize text as per the request parameters and
67 * return synthesized data via the supplied callback. This class and its helpers
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +010068 * will then consume that data, which might mean queuing it for playback or writing
69 * it to a file or similar. All calls to this method will be on a single thread,
70 * which will be different from the main thread of the service. Synthesis must be
71 * synchronous which means the engine must NOT hold on to the callback or call any
72 * methods on it after the method returns.
Narayan Kamathe22b69a2011-06-08 11:41:47 +010073 *
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +010074 * {@link #onStop} tells the engine that it should stop
75 * all ongoing synthesis, if any. Any pending data from the current synthesis
76 * will be discarded.
Narayan Kamathe5b8c4d2011-08-22 15:37:47 +010077 *
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +010078 * {@link #onGetLanguage} is not required as of JELLYBEAN_MR2 (API 18) and later, it is only
79 * called on earlier versions of Android.
Przemyslaw Szczepaniakad6df742014-07-01 17:04:25 +010080 *
81 * API Level 20 adds support for Voice objects. Voices are an abstraction that allow the TTS
82 * service to expose multiple backends for a single locale. Each one of them can have a different
83 * features set. In order to fully take advantage of voices, an engine should implement
84 * the following methods:
85 * <ul>
86 * <li>{@link #onGetVoices()}</li>
Przemyslaw Szczepaniak35c76982014-09-05 15:36:49 +010087 * <li>{@link #onIsValidVoiceName(String)}</li>
Przemyslaw Szczepaniakad6df742014-07-01 17:04:25 +010088 * <li>{@link #onLoadVoice(String)}</li>
89 * <li>{@link #onGetDefaultVoiceNameFor(String, String, String)}</li>
90 * </ul>
91 * The first three methods are siblings of the {@link #onGetLanguage},
92 * {@link #onIsLanguageAvailable} and {@link #onLoadLanguage} methods. The last one,
93 * {@link #onGetDefaultVoiceNameFor(String, String, String)} is a link between locale and voice
Przemyslaw Szczepaniak6ddd9c22014-09-25 10:58:48 +010094 * based methods. Since API level 21 {@link TextToSpeech#setLanguage} is implemented by
Przemyslaw Szczepaniakad6df742014-07-01 17:04:25 +010095 * calling {@link TextToSpeech#setVoice} with the voice returned by
96 * {@link #onGetDefaultVoiceNameFor(String, String, String)}.
97 *
98 * If the client uses a voice instead of a locale, {@link SynthesisRequest} will contain the
99 * requested voice name.
100 *
101 * The default implementations of Voice-related methods implement them using the
102 * pre-existing locale-based implementation.
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000103 */
104public abstract class TextToSpeechService extends Service {
105
106 private static final boolean DBG = false;
107 private static final String TAG = "TextToSpeechService";
108
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000109 private static final String SYNTH_THREAD_NAME = "SynthThread";
110
111 private SynthHandler mSynthHandler;
Narayan Kamath8d1fc242011-06-03 18:11:54 +0100112 // A thread and it's associated handler for playing back any audio
113 // associated with this TTS engine. Will handle all requests except synthesis
114 // to file requests, which occur on the synthesis thread.
Niels Egbertsc99ba1c2015-11-20 11:44:25 +0000115 @NonNull private AudioPlaybackHandler mAudioPlaybackHandler;
Narayan Kamathe5b8c4d2011-08-22 15:37:47 +0100116 private TtsEngines mEngineHelper;
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000117
118 private CallbackMap mCallbacks;
Narayan Kamath6dabb632011-07-08 12:13:03 +0100119 private String mPackageName;
Narayan Kamath7a3af862011-06-02 17:28:57 +0100120
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +0100121 private final Object mVoicesInfoLock = new Object();
122
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000123 @Override
124 public void onCreate() {
125 if (DBG) Log.d(TAG, "onCreate()");
126 super.onCreate();
127
128 SynthThread synthThread = new SynthThread();
129 synthThread.start();
130 mSynthHandler = new SynthHandler(synthThread.getLooper());
131
Narayan Kamath4924fe32011-06-09 11:35:13 +0100132 mAudioPlaybackHandler = new AudioPlaybackHandler();
133 mAudioPlaybackHandler.start();
Narayan Kamathc90f1c82011-05-24 11:39:43 +0100134
Narayan Kamathe5b8c4d2011-08-22 15:37:47 +0100135 mEngineHelper = new TtsEngines(this);
136
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000137 mCallbacks = new CallbackMap();
138
Narayan Kamath6dabb632011-07-08 12:13:03 +0100139 mPackageName = getApplicationInfo().packageName;
140
Narayan Kamathe5b8c4d2011-08-22 15:37:47 +0100141 String[] defaultLocale = getSettingsLocale();
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +0100142
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000143 // Load default language
Narayan Kamathe5b8c4d2011-08-22 15:37:47 +0100144 onLoadLanguage(defaultLocale[0], defaultLocale[1], defaultLocale[2]);
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000145 }
146
147 @Override
148 public void onDestroy() {
149 if (DBG) Log.d(TAG, "onDestroy()");
150
151 // Tell the synthesizer to stop
152 mSynthHandler.quit();
Narayan Kamath8d1fc242011-06-03 18:11:54 +0100153 // Tell the audio playback thread to stop.
154 mAudioPlaybackHandler.quit();
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000155 // Unregister all callbacks.
156 mCallbacks.kill();
157
158 super.onDestroy();
159 }
160
161 /**
162 * Checks whether the engine supports a given language.
163 *
164 * Can be called on multiple threads.
165 *
Przemyslaw Szczepaniak13896b72012-11-09 15:18:16 +0000166 * Its return values HAVE to be consistent with onLoadLanguage.
167 *
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000168 * @param lang ISO-3 language code.
169 * @param country ISO-3 country code. May be empty or null.
170 * @param variant Language variant. May be empty or null.
171 * @return Code indicating the support status for the locale.
172 * One of {@link TextToSpeech#LANG_AVAILABLE},
173 * {@link TextToSpeech#LANG_COUNTRY_AVAILABLE},
174 * {@link TextToSpeech#LANG_COUNTRY_VAR_AVAILABLE},
175 * {@link TextToSpeech#LANG_MISSING_DATA}
176 * {@link TextToSpeech#LANG_NOT_SUPPORTED}.
177 */
178 protected abstract int onIsLanguageAvailable(String lang, String country, String variant);
179
180 /**
181 * Returns the language, country and variant currently being used by the TTS engine.
182 *
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +0100183 * This method will be called only on Android 4.2 and before (API <= 17). In later versions
184 * this method is not called by the Android TTS framework.
185 *
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000186 * Can be called on multiple threads.
187 *
188 * @return A 3-element array, containing language (ISO 3-letter code),
189 * country (ISO 3-letter code) and variant used by the engine.
190 * The country and variant may be {@code ""}. If country is empty, then variant must
191 * be empty too.
192 * @see Locale#getISO3Language()
193 * @see Locale#getISO3Country()
194 * @see Locale#getVariant()
195 */
196 protected abstract String[] onGetLanguage();
197
198 /**
199 * Notifies the engine that it should load a speech synthesis language. There is no guarantee
200 * that this method is always called before the language is used for synthesis. It is merely
201 * a hint to the engine that it will probably get some synthesis requests for this language
202 * at some point in the future.
203 *
204 * Can be called on multiple threads.
Przemyslaw Szczepaniak13896b72012-11-09 15:18:16 +0000205 * In <= Android 4.2 (<= API 17) can be called on main and service binder threads.
206 * In > Android 4.2 (> API 17) can be called on main and synthesis threads.
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000207 *
208 * @param lang ISO-3 language code.
209 * @param country ISO-3 country code. May be empty or null.
210 * @param variant Language variant. May be empty or null.
211 * @return Code indicating the support status for the locale.
212 * One of {@link TextToSpeech#LANG_AVAILABLE},
213 * {@link TextToSpeech#LANG_COUNTRY_AVAILABLE},
214 * {@link TextToSpeech#LANG_COUNTRY_VAR_AVAILABLE},
215 * {@link TextToSpeech#LANG_MISSING_DATA}
216 * {@link TextToSpeech#LANG_NOT_SUPPORTED}.
217 */
218 protected abstract int onLoadLanguage(String lang, String country, String variant);
219
220 /**
221 * Notifies the service that it should stop any in-progress speech synthesis.
222 * This method can be called even if no speech synthesis is currently in progress.
223 *
224 * Can be called on multiple threads, but not on the synthesis thread.
225 */
226 protected abstract void onStop();
227
228 /**
Niels Egberts01dedf52016-12-23 15:32:11 +0000229 * Tells the service to synthesize speech from the given text. This method should block until
230 * the synthesis is finished. Called on the synthesis thread.
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000231 *
Narayan Kamathe22b69a2011-06-08 11:41:47 +0100232 * @param request The synthesis request.
Niels Egberts01dedf52016-12-23 15:32:11 +0000233 * @param callback The callback that the engine must use to make data available for playback or
234 * for writing to a file.
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000235 */
Niels Egberts01dedf52016-12-23 15:32:11 +0000236 protected abstract void onSynthesizeText(SynthesisRequest request, SynthesisCallback callback);
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000237
Narayan Kamath748af662011-10-31 14:20:01 +0000238 /**
239 * Queries the service for a set of features supported for a given language.
240 *
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +0100241 * Can be called on multiple threads.
242 *
Narayan Kamath748af662011-10-31 14:20:01 +0000243 * @param lang ISO-3 language code.
244 * @param country ISO-3 country code. May be empty or null.
245 * @param variant Language variant. May be empty or null.
246 * @return A list of features supported for the given language.
247 */
248 protected Set<String> onGetFeaturesForLanguage(String lang, String country, String variant) {
Niels Egberts54545f72015-08-05 15:41:18 +0100249 return new HashSet<String>();
Narayan Kamath748af662011-10-31 14:20:01 +0000250 }
251
Przemyslaw Szczepaniakad6df742014-07-01 17:04:25 +0100252 private int getExpectedLanguageAvailableStatus(Locale locale) {
253 int expectedStatus = TextToSpeech.LANG_COUNTRY_VAR_AVAILABLE;
254 if (locale.getVariant().isEmpty()) {
255 if (locale.getCountry().isEmpty()) {
256 expectedStatus = TextToSpeech.LANG_AVAILABLE;
257 } else {
258 expectedStatus = TextToSpeech.LANG_COUNTRY_AVAILABLE;
259 }
260 }
261 return expectedStatus;
262 }
263
264 /**
265 * Queries the service for a set of supported voices.
266 *
267 * Can be called on multiple threads.
268 *
269 * The default implementation tries to enumerate all available locales, pass them to
270 * {@link #onIsLanguageAvailable(String, String, String)} and create Voice instances (using
271 * the locale's BCP-47 language tag as the voice name) for the ones that are supported.
272 * Note, that this implementation is suitable only for engines that don't have multiple voices
273 * for a single locale. Also, this implementation won't work with Locales not listed in the
274 * set returned by the {@link Locale#getAvailableLocales()} method.
275 *
276 * @return A list of voices supported.
277 */
Przemyslaw Szczepaniak35c76982014-09-05 15:36:49 +0100278 public List<Voice> onGetVoices() {
Przemyslaw Szczepaniakad6df742014-07-01 17:04:25 +0100279 // Enumerate all locales and check if they are available
280 ArrayList<Voice> voices = new ArrayList<Voice>();
281 for (Locale locale : Locale.getAvailableLocales()) {
282 int expectedStatus = getExpectedLanguageAvailableStatus(locale);
283 try {
284 int localeStatus = onIsLanguageAvailable(locale.getISO3Language(),
285 locale.getISO3Country(), locale.getVariant());
286 if (localeStatus != expectedStatus) {
287 continue;
288 }
289 } catch (MissingResourceException e) {
290 // Ignore locale without iso 3 codes
291 continue;
292 }
293 Set<String> features = onGetFeaturesForLanguage(locale.getISO3Language(),
294 locale.getISO3Country(), locale.getVariant());
Niels Egberts992ea152015-06-26 16:00:43 +0100295 String voiceName = onGetDefaultVoiceNameFor(locale.getISO3Language(),
296 locale.getISO3Country(), locale.getVariant());
297 voices.add(new Voice(voiceName, locale, Voice.QUALITY_NORMAL,
Przemyslaw Szczepaniakad6df742014-07-01 17:04:25 +0100298 Voice.LATENCY_NORMAL, false, features));
299 }
300 return voices;
301 }
302
303 /**
304 * Return a name of the default voice for a given locale.
305 *
306 * This method provides a mapping between locales and available voices. This method is
307 * used in {@link TextToSpeech#setLanguage}, which calls this method and then calls
308 * {@link TextToSpeech#setVoice} with the voice returned by this method.
309 *
310 * Also, it's used by {@link TextToSpeech#getDefaultVoice()} to find a default voice for
311 * the default locale.
312 *
313 * @param lang ISO-3 language code.
314 * @param country ISO-3 country code. May be empty or null.
315 * @param variant Language variant. May be empty or null.
316
317 * @return A name of the default voice for a given locale.
318 */
Przemyslaw Szczepaniakf9ba5482014-09-22 17:39:16 +0100319 public String onGetDefaultVoiceNameFor(String lang, String country, String variant) {
Przemyslaw Szczepaniakad6df742014-07-01 17:04:25 +0100320 int localeStatus = onIsLanguageAvailable(lang, country, variant);
321 Locale iso3Locale = null;
322 switch (localeStatus) {
323 case TextToSpeech.LANG_AVAILABLE:
324 iso3Locale = new Locale(lang);
325 break;
326 case TextToSpeech.LANG_COUNTRY_AVAILABLE:
327 iso3Locale = new Locale(lang, country);
328 break;
329 case TextToSpeech.LANG_COUNTRY_VAR_AVAILABLE:
330 iso3Locale = new Locale(lang, country, variant);
331 break;
332 default:
333 return null;
334 }
335 Locale properLocale = TtsEngines.normalizeTTSLocale(iso3Locale);
336 String voiceName = properLocale.toLanguageTag();
Przemyslaw Szczepaniak35c76982014-09-05 15:36:49 +0100337 if (onIsValidVoiceName(voiceName) == TextToSpeech.SUCCESS) {
Przemyslaw Szczepaniakad6df742014-07-01 17:04:25 +0100338 return voiceName;
339 } else {
340 return null;
341 }
342 }
343
344 /**
345 * Notifies the engine that it should load a speech synthesis voice. There is no guarantee
346 * that this method is always called before the voice is used for synthesis. It is merely
347 * a hint to the engine that it will probably get some synthesis requests for this voice
348 * at some point in the future.
349 *
350 * Will be called only on synthesis thread.
351 *
352 * The default implementation creates a Locale from the voice name (by interpreting the name as
353 * a BCP-47 tag for the locale), and passes it to
354 * {@link #onLoadLanguage(String, String, String)}.
355 *
356 * @param voiceName Name of the voice.
357 * @return {@link TextToSpeech#ERROR} or {@link TextToSpeech#SUCCESS}.
358 */
Przemyslaw Szczepaniak35c76982014-09-05 15:36:49 +0100359 public int onLoadVoice(String voiceName) {
Przemyslaw Szczepaniakad6df742014-07-01 17:04:25 +0100360 Locale locale = Locale.forLanguageTag(voiceName);
361 if (locale == null) {
362 return TextToSpeech.ERROR;
363 }
364 int expectedStatus = getExpectedLanguageAvailableStatus(locale);
365 try {
366 int localeStatus = onIsLanguageAvailable(locale.getISO3Language(),
367 locale.getISO3Country(), locale.getVariant());
368 if (localeStatus != expectedStatus) {
369 return TextToSpeech.ERROR;
370 }
371 onLoadLanguage(locale.getISO3Language(),
372 locale.getISO3Country(), locale.getVariant());
373 return TextToSpeech.SUCCESS;
374 } catch (MissingResourceException e) {
375 return TextToSpeech.ERROR;
376 }
377 }
378
379 /**
380 * Checks whether the engine supports a voice with a given name.
381 *
382 * Can be called on multiple threads.
383 *
384 * The default implementation treats the voice name as a language tag, creating a Locale from
385 * the voice name, and passes it to {@link #onIsLanguageAvailable(String, String, String)}.
386 *
387 * @param voiceName Name of the voice.
388 * @return {@link TextToSpeech#ERROR} or {@link TextToSpeech#SUCCESS}.
389 */
Przemyslaw Szczepaniak35c76982014-09-05 15:36:49 +0100390 public int onIsValidVoiceName(String voiceName) {
Przemyslaw Szczepaniakad6df742014-07-01 17:04:25 +0100391 Locale locale = Locale.forLanguageTag(voiceName);
392 if (locale == null) {
393 return TextToSpeech.ERROR;
394 }
395 int expectedStatus = getExpectedLanguageAvailableStatus(locale);
396 try {
397 int localeStatus = onIsLanguageAvailable(locale.getISO3Language(),
398 locale.getISO3Country(), locale.getVariant());
399 if (localeStatus != expectedStatus) {
400 return TextToSpeech.ERROR;
401 }
402 return TextToSpeech.SUCCESS;
403 } catch (MissingResourceException e) {
404 return TextToSpeech.ERROR;
405 }
406 }
407
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000408 private int getDefaultSpeechRate() {
409 return getSecureSettingInt(Settings.Secure.TTS_DEFAULT_RATE, Engine.DEFAULT_RATE);
410 }
411
Sergio Sanchob0cde2c2017-01-26 20:39:05 +0000412 private int getDefaultPitch() {
413 return getSecureSettingInt(Settings.Secure.TTS_DEFAULT_PITCH, Engine.DEFAULT_PITCH);
414 }
415
Narayan Kamathe5b8c4d2011-08-22 15:37:47 +0100416 private String[] getSettingsLocale() {
Przemyslaw Szczepaniak1b5637e2014-05-30 11:00:17 +0100417 final Locale locale = mEngineHelper.getLocalePrefForEngine(mPackageName);
418 return TtsEngines.toOldLocaleStringFormat(locale);
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000419 }
420
421 private int getSecureSettingInt(String name, int defaultValue) {
422 return Settings.Secure.getInt(getContentResolver(), name, defaultValue);
423 }
424
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000425 /**
426 * Synthesizer thread. This thread is used to run {@link SynthHandler}.
427 */
428 private class SynthThread extends HandlerThread implements MessageQueue.IdleHandler {
429
430 private boolean mFirstIdle = true;
431
432 public SynthThread() {
Narayan Kamath84deb602011-07-27 13:22:09 +0100433 super(SYNTH_THREAD_NAME, android.os.Process.THREAD_PRIORITY_DEFAULT);
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000434 }
435
436 @Override
437 protected void onLooperPrepared() {
438 getLooper().getQueue().addIdleHandler(this);
439 }
440
441 @Override
442 public boolean queueIdle() {
443 if (mFirstIdle) {
444 mFirstIdle = false;
445 } else {
446 broadcastTtsQueueProcessingCompleted();
447 }
448 return true;
449 }
450
451 private void broadcastTtsQueueProcessingCompleted() {
452 Intent i = new Intent(TextToSpeech.ACTION_TTS_QUEUE_PROCESSING_COMPLETED);
453 if (DBG) Log.d(TAG, "Broadcasting: " + i);
454 sendBroadcast(i);
455 }
456 }
457
458 private class SynthHandler extends Handler {
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000459 private SpeechItem mCurrentSpeechItem = null;
460
Niels Egberts10db95b2016-04-07 12:34:55 +0100461 // When a message with QUEUE_FLUSH arrives we add the caller identity to the List and when a
462 // message with QUEUE_DESTROY arrives we increment mFlushAll. Then a message is added to the
463 // handler queue that removes the caller identify from the list and decrements the mFlushAll
464 // counter. This is so that when a message is processed and the caller identity is in the
465 // list or mFlushAll is not zero, we know that the message should be flushed.
466 // It's important that mFlushedObjects is a List and not a Set, and that mFlushAll is an
467 // int and not a bool. This is because when multiple messages arrive with QUEUE_FLUSH or
468 // QUEUE_DESTROY, we want to keep flushing messages until we arrive at the last QUEUE_FLUSH
469 // or QUEUE_DESTROY message.
470 private List<Object> mFlushedObjects = new ArrayList<>();
471 private int mFlushAll = 0;
Przemyslaw Szczepaniak4b738672014-11-19 17:36:57 +0000472
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000473 public SynthHandler(Looper looper) {
474 super(looper);
475 }
476
Przemyslaw Szczepaniak4b738672014-11-19 17:36:57 +0000477 private void startFlushingSpeechItems(Object callerIdentity) {
478 synchronized (mFlushedObjects) {
479 if (callerIdentity == null) {
Niels Egberts10db95b2016-04-07 12:34:55 +0100480 mFlushAll += 1;
Przemyslaw Szczepaniak4b738672014-11-19 17:36:57 +0000481 } else {
482 mFlushedObjects.add(callerIdentity);
483 }
484 }
485 }
486 private void endFlushingSpeechItems(Object callerIdentity) {
487 synchronized (mFlushedObjects) {
488 if (callerIdentity == null) {
Niels Egberts10db95b2016-04-07 12:34:55 +0100489 mFlushAll -= 1;
Przemyslaw Szczepaniak4b738672014-11-19 17:36:57 +0000490 } else {
491 mFlushedObjects.remove(callerIdentity);
492 }
493 }
494 }
495 private boolean isFlushed(SpeechItem speechItem) {
496 synchronized (mFlushedObjects) {
Niels Egberts10db95b2016-04-07 12:34:55 +0100497 return mFlushAll > 0 || mFlushedObjects.contains(speechItem.getCallerIdentity());
Przemyslaw Szczepaniak4b738672014-11-19 17:36:57 +0000498 }
499 }
500
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000501 private synchronized SpeechItem getCurrentSpeechItem() {
502 return mCurrentSpeechItem;
503 }
504
505 private synchronized SpeechItem setCurrentSpeechItem(SpeechItem speechItem) {
506 SpeechItem old = mCurrentSpeechItem;
507 mCurrentSpeechItem = speechItem;
508 return old;
509 }
510
Narayan Kamath492b7f02011-11-29 17:02:06 +0000511 private synchronized SpeechItem maybeRemoveCurrentSpeechItem(Object callerIdentity) {
Narayan Kamatha65c62a2011-09-06 17:04:48 +0100512 if (mCurrentSpeechItem != null &&
Przemyslaw Szczepaniak13896b72012-11-09 15:18:16 +0000513 (mCurrentSpeechItem.getCallerIdentity() == callerIdentity)) {
Narayan Kamatha65c62a2011-09-06 17:04:48 +0100514 SpeechItem current = mCurrentSpeechItem;
515 mCurrentSpeechItem = null;
516 return current;
517 }
518
519 return null;
520 }
521
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000522 public boolean isSpeaking() {
523 return getCurrentSpeechItem() != null;
524 }
525
526 public void quit() {
527 // Don't process any more speech items
528 getLooper().quit();
529 // Stop the current speech item
530 SpeechItem current = setCurrentSpeechItem(null);
531 if (current != null) {
532 current.stop();
533 }
Narayan Kamathbe4ad4a2011-07-15 13:01:09 +0100534 // The AudioPlaybackHandler will be destroyed by the caller.
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000535 }
536
537 /**
538 * Adds a speech item to the queue.
539 *
540 * Called on a service binder thread.
541 */
542 public int enqueueSpeechItem(int queueMode, final SpeechItem speechItem) {
Przemyslaw Szczepaniak13896b72012-11-09 15:18:16 +0000543 UtteranceProgressDispatcher utterenceProgress = null;
544 if (speechItem instanceof UtteranceProgressDispatcher) {
545 utterenceProgress = (UtteranceProgressDispatcher) speechItem;
546 }
547
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000548 if (!speechItem.isValid()) {
Przemyslaw Szczepaniak13896b72012-11-09 15:18:16 +0000549 if (utterenceProgress != null) {
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +0100550 utterenceProgress.dispatchOnError(
Przemyslaw Szczepaniakfc4b2892014-06-26 11:52:20 +0100551 TextToSpeech.ERROR_INVALID_REQUEST);
Przemyslaw Szczepaniak13896b72012-11-09 15:18:16 +0000552 }
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000553 return TextToSpeech.ERROR;
554 }
Narayan Kamath4924fe32011-06-09 11:35:13 +0100555
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000556 if (queueMode == TextToSpeech.QUEUE_FLUSH) {
Narayan Kamath492b7f02011-11-29 17:02:06 +0000557 stopForApp(speechItem.getCallerIdentity());
Narayan Kamathabc63fb2011-06-10 11:36:57 +0100558 } else if (queueMode == TextToSpeech.QUEUE_DESTROY) {
Narayan Kamatha65c62a2011-09-06 17:04:48 +0100559 stopAll();
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000560 }
561 Runnable runnable = new Runnable() {
562 @Override
563 public void run() {
Przemyslaw Szczepaniak4b738672014-11-19 17:36:57 +0000564 if (isFlushed(speechItem)) {
565 speechItem.stop();
566 } else {
567 setCurrentSpeechItem(speechItem);
568 speechItem.play();
569 setCurrentSpeechItem(null);
570 }
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000571 }
572 };
573 Message msg = Message.obtain(this, runnable);
Przemyslaw Szczepaniak13896b72012-11-09 15:18:16 +0000574
Narayan Kamatha65c62a2011-09-06 17:04:48 +0100575 // The obj is used to remove all callbacks from the given app in
576 // stopForApp(String).
Narayan Kamathabc63fb2011-06-10 11:36:57 +0100577 //
578 // Note that this string is interned, so the == comparison works.
Narayan Kamath492b7f02011-11-29 17:02:06 +0000579 msg.obj = speechItem.getCallerIdentity();
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +0100580
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000581 if (sendMessage(msg)) {
582 return TextToSpeech.SUCCESS;
583 } else {
584 Log.w(TAG, "SynthThread has quit");
Przemyslaw Szczepaniak13896b72012-11-09 15:18:16 +0000585 if (utterenceProgress != null) {
Przemyslaw Szczepaniakfc4b2892014-06-26 11:52:20 +0100586 utterenceProgress.dispatchOnError(TextToSpeech.ERROR_SERVICE);
Przemyslaw Szczepaniak13896b72012-11-09 15:18:16 +0000587 }
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000588 return TextToSpeech.ERROR;
589 }
590 }
591
592 /**
593 * Stops all speech output and removes any utterances still in the queue for
594 * the calling app.
595 *
596 * Called on a service binder thread.
597 */
Przemyslaw Szczepaniak4b738672014-11-19 17:36:57 +0000598 public int stopForApp(final Object callerIdentity) {
Narayan Kamath492b7f02011-11-29 17:02:06 +0000599 if (callerIdentity == null) {
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000600 return TextToSpeech.ERROR;
601 }
Narayan Kamath4924fe32011-06-09 11:35:13 +0100602
Przemyslaw Szczepaniak4b738672014-11-19 17:36:57 +0000603 // Flush pending messages from callerIdentity
604 startFlushingSpeechItems(callerIdentity);
605
Narayan Kamathbe4ad4a2011-07-15 13:01:09 +0100606 // This stops writing data to the file / or publishing
607 // items to the audio playback handler.
Narayan Kamatha65c62a2011-09-06 17:04:48 +0100608 //
609 // Note that the current speech item must be removed only if it
610 // belongs to the callingApp, else the item will be "orphaned" and
611 // not stopped correctly if a stop request comes along for the item
612 // from the app it belongs to.
Narayan Kamath492b7f02011-11-29 17:02:06 +0000613 SpeechItem current = maybeRemoveCurrentSpeechItem(callerIdentity);
Narayan Kamatha65c62a2011-09-06 17:04:48 +0100614 if (current != null) {
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000615 current.stop();
616 }
Narayan Kamath8d1fc242011-06-03 18:11:54 +0100617
Narayan Kamath4924fe32011-06-09 11:35:13 +0100618 // Remove any enqueued audio too.
Narayan Kamath67ae6bc2011-11-30 14:51:00 +0000619 mAudioPlaybackHandler.stopForApp(callerIdentity);
Narayan Kamath4924fe32011-06-09 11:35:13 +0100620
Przemyslaw Szczepaniak4b738672014-11-19 17:36:57 +0000621 // Stop flushing pending messages
622 Runnable runnable = new Runnable() {
623 @Override
624 public void run() {
625 endFlushingSpeechItems(callerIdentity);
626 }
627 };
628 sendMessage(Message.obtain(this, runnable));
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000629 return TextToSpeech.SUCCESS;
630 }
Narayan Kamatha65c62a2011-09-06 17:04:48 +0100631
632 public int stopAll() {
Przemyslaw Szczepaniak4b738672014-11-19 17:36:57 +0000633 // Order to flush pending messages
634 startFlushingSpeechItems(null);
635
Przemyslaw Szczepaniak13896b72012-11-09 15:18:16 +0000636 // Stop the current speech item unconditionally .
Narayan Kamatha65c62a2011-09-06 17:04:48 +0100637 SpeechItem current = setCurrentSpeechItem(null);
638 if (current != null) {
639 current.stop();
640 }
Narayan Kamatha65c62a2011-09-06 17:04:48 +0100641 // Remove all pending playback as well.
Narayan Kamath67ae6bc2011-11-30 14:51:00 +0000642 mAudioPlaybackHandler.stop();
Narayan Kamatha65c62a2011-09-06 17:04:48 +0100643
Przemyslaw Szczepaniak4b738672014-11-19 17:36:57 +0000644 // Message to stop flushing pending messages
645 Runnable runnable = new Runnable() {
646 @Override
647 public void run() {
648 endFlushingSpeechItems(null);
649 }
650 };
651 sendMessage(Message.obtain(this, runnable));
652
653
Narayan Kamatha65c62a2011-09-06 17:04:48 +0100654 return TextToSpeech.SUCCESS;
655 }
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000656 }
657
Narayan Kamath754c72e2011-11-09 14:22:32 +0000658 interface UtteranceProgressDispatcher {
Niels Egberts2c80a0382016-12-23 12:18:22 +0000659 void dispatchOnStop();
660
661 void dispatchOnSuccess();
662
663 void dispatchOnStart();
664
665 void dispatchOnError(int errorCode);
666
667 void dispatchOnBeginSynthesis(int sampleRateInHz, int audioFormat, int channelCount);
668
669 void dispatchOnAudioAvailable(byte[] audio);
Niels Egberts65c50782016-12-23 12:01:32 +0000670
Niels Egberts5d0ea0f2017-03-21 15:29:24 +0000671 public void dispatchOnRangeStart(int start, int end, int frame);
Narayan Kamath8d1fc242011-06-03 18:11:54 +0100672 }
673
Przemyslaw Szczepaniak5cbf17c2014-06-18 11:35:52 +0100674 /** Set of parameters affecting audio output. */
675 static class AudioOutputParams {
676 /**
677 * Audio session identifier. May be used to associate audio playback with one of the
678 * {@link android.media.audiofx.AudioEffect} objects. If not specified by client,
Glenn Kasten33b84042016-03-08 12:02:55 -0800679 * it should be equal to {@link AudioManager#AUDIO_SESSION_ID_GENERATE}.
Przemyslaw Szczepaniak5cbf17c2014-06-18 11:35:52 +0100680 */
681 public final int mSessionId;
682
683 /**
Przemyslaw Szczepaniak5cbf17c2014-06-18 11:35:52 +0100684 * Volume, in the range [0.0f, 1.0f]. The default value is
685 * {@link TextToSpeech.Engine#DEFAULT_VOLUME} (1.0f).
686 */
687 public final float mVolume;
688
689 /**
690 * Left/right position of the audio, in the range [-1.0f, 1.0f].
691 * The default value is {@link TextToSpeech.Engine#DEFAULT_PAN} (0.0f).
692 */
693 public final float mPan;
694
Przemyslaw Szczepaniak672695e2014-07-14 17:10:42 +0100695
696 /**
697 * Audio attributes, set by {@link TextToSpeech#setAudioAttributes}
698 * or created from the value of {@link TextToSpeech.Engine#KEY_PARAM_STREAM}.
699 */
700 public final AudioAttributes mAudioAttributes;
701
Przemyslaw Szczepaniak5cbf17c2014-06-18 11:35:52 +0100702 /** Create AudioOutputParams with default values */
703 AudioOutputParams() {
Glenn Kasten33b84042016-03-08 12:02:55 -0800704 mSessionId = AudioManager.AUDIO_SESSION_ID_GENERATE;
Przemyslaw Szczepaniak5cbf17c2014-06-18 11:35:52 +0100705 mVolume = Engine.DEFAULT_VOLUME;
706 mPan = Engine.DEFAULT_PAN;
Przemyslaw Szczepaniak672695e2014-07-14 17:10:42 +0100707 mAudioAttributes = null;
Przemyslaw Szczepaniak5cbf17c2014-06-18 11:35:52 +0100708 }
709
Przemyslaw Szczepaniak672695e2014-07-14 17:10:42 +0100710 AudioOutputParams(int sessionId, float volume, float pan,
711 AudioAttributes audioAttributes) {
Przemyslaw Szczepaniak5cbf17c2014-06-18 11:35:52 +0100712 mSessionId = sessionId;
Przemyslaw Szczepaniak5cbf17c2014-06-18 11:35:52 +0100713 mVolume = volume;
714 mPan = pan;
Przemyslaw Szczepaniak672695e2014-07-14 17:10:42 +0100715 mAudioAttributes = audioAttributes;
Przemyslaw Szczepaniak5cbf17c2014-06-18 11:35:52 +0100716 }
717
718 /** Create AudioOutputParams from A {@link SynthesisRequest#getParams()} bundle */
Niels Egberts01dedf52016-12-23 15:32:11 +0000719 static AudioOutputParams createFromParamsBundle(Bundle paramsBundle, boolean isSpeech) {
Przemyslaw Szczepaniak5cbf17c2014-06-18 11:35:52 +0100720 if (paramsBundle == null) {
721 return new AudioOutputParams();
722 }
723
Przemyslaw Szczepaniak672695e2014-07-14 17:10:42 +0100724 AudioAttributes audioAttributes =
725 (AudioAttributes) paramsBundle.getParcelable(
726 Engine.KEY_PARAM_AUDIO_ATTRIBUTES);
727 if (audioAttributes == null) {
728 int streamType = paramsBundle.getInt(
729 Engine.KEY_PARAM_STREAM, Engine.DEFAULT_STREAM);
730 audioAttributes = (new AudioAttributes.Builder())
731 .setLegacyStreamType(streamType)
732 .setContentType((isSpeech ?
733 AudioAttributes.CONTENT_TYPE_SPEECH :
734 AudioAttributes.CONTENT_TYPE_SONIFICATION))
735 .build();
736 }
737
Przemyslaw Szczepaniak5cbf17c2014-06-18 11:35:52 +0100738 return new AudioOutputParams(
739 paramsBundle.getInt(
740 Engine.KEY_PARAM_SESSION_ID,
Glenn Kasten33b84042016-03-08 12:02:55 -0800741 AudioManager.AUDIO_SESSION_ID_GENERATE),
Przemyslaw Szczepaniak5cbf17c2014-06-18 11:35:52 +0100742 paramsBundle.getFloat(
743 Engine.KEY_PARAM_VOLUME,
744 Engine.DEFAULT_VOLUME),
745 paramsBundle.getFloat(
746 Engine.KEY_PARAM_PAN,
Przemyslaw Szczepaniak672695e2014-07-14 17:10:42 +0100747 Engine.DEFAULT_PAN),
748 audioAttributes);
Przemyslaw Szczepaniak5cbf17c2014-06-18 11:35:52 +0100749 }
Przemyslaw Szczepaniak5cbf17c2014-06-18 11:35:52 +0100750 }
751
752
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000753 /**
754 * An item in the synth thread queue.
755 */
Przemyslaw Szczepaniak13896b72012-11-09 15:18:16 +0000756 private abstract class SpeechItem {
Narayan Kamath492b7f02011-11-29 17:02:06 +0000757 private final Object mCallerIdentity;
Narayan Kamath492b7f02011-11-29 17:02:06 +0000758 private final int mCallerUid;
759 private final int mCallerPid;
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000760 private boolean mStarted = false;
761 private boolean mStopped = false;
762
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +0100763 public SpeechItem(Object caller, int callerUid, int callerPid) {
Narayan Kamath492b7f02011-11-29 17:02:06 +0000764 mCallerIdentity = caller;
Narayan Kamath492b7f02011-11-29 17:02:06 +0000765 mCallerUid = callerUid;
766 mCallerPid = callerPid;
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000767 }
768
Narayan Kamath492b7f02011-11-29 17:02:06 +0000769 public Object getCallerIdentity() {
770 return mCallerIdentity;
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000771 }
772
Przemyslaw Szczepaniak13896b72012-11-09 15:18:16 +0000773 public int getCallerUid() {
774 return mCallerUid;
775 }
776
777 public int getCallerPid() {
778 return mCallerPid;
779 }
780
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000781 /**
782 * Checker whether the item is valid. If this method returns false, the item should not
783 * be played.
784 */
785 public abstract boolean isValid();
786
787 /**
788 * Plays the speech item. Blocks until playback is finished.
789 * Must not be called more than once.
790 *
791 * Only called on the synthesis thread.
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000792 */
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +0100793 public void play() {
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000794 synchronized (this) {
795 if (mStarted) {
796 throw new IllegalStateException("play() called twice");
797 }
798 mStarted = true;
799 }
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +0100800 playImpl();
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000801 }
802
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +0100803 protected abstract void playImpl();
Przemyslaw Szczepaniak13896b72012-11-09 15:18:16 +0000804
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000805 /**
806 * Stops the speech item.
807 * Must not be called more than once.
808 *
809 * Can be called on multiple threads, but not on the synthesis thread.
810 */
811 public void stop() {
812 synchronized (this) {
813 if (mStopped) {
814 throw new IllegalStateException("stop() called twice");
815 }
816 mStopped = true;
817 }
818 stopImpl();
819 }
820
Przemyslaw Szczepaniak13896b72012-11-09 15:18:16 +0000821 protected abstract void stopImpl();
822
823 protected synchronized boolean isStopped() {
824 return mStopped;
825 }
Przemyslaw Szczepaniak4b738672014-11-19 17:36:57 +0000826
827 protected synchronized boolean isStarted() {
828 return mStarted;
829 }
Przemyslaw Szczepaniak13896b72012-11-09 15:18:16 +0000830 }
831
832 /**
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +0100833 * An item in the synth thread queue that process utterance (and call back to client about
834 * progress).
Przemyslaw Szczepaniak13896b72012-11-09 15:18:16 +0000835 */
836 private abstract class UtteranceSpeechItem extends SpeechItem
837 implements UtteranceProgressDispatcher {
838
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +0100839 public UtteranceSpeechItem(Object caller, int callerUid, int callerPid) {
840 super(caller, callerUid, callerPid);
Przemyslaw Szczepaniak13896b72012-11-09 15:18:16 +0000841 }
842
Narayan Kamath754c72e2011-11-09 14:22:32 +0000843 @Override
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +0100844 public void dispatchOnSuccess() {
Narayan Kamath8d1fc242011-06-03 18:11:54 +0100845 final String utteranceId = getUtteranceId();
Narayan Kamath68e2af52011-11-28 17:10:04 +0000846 if (utteranceId != null) {
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +0100847 mCallbacks.dispatchOnSuccess(getCallerIdentity(), utteranceId);
848 }
849 }
850
851 @Override
852 public void dispatchOnStop() {
853 final String utteranceId = getUtteranceId();
854 if (utteranceId != null) {
Przemyslaw Szczepaniak4b738672014-11-19 17:36:57 +0000855 mCallbacks.dispatchOnStop(getCallerIdentity(), utteranceId, isStarted());
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +0100856 }
857 }
858
859 @Override
Narayan Kamath754c72e2011-11-09 14:22:32 +0000860 public void dispatchOnStart() {
861 final String utteranceId = getUtteranceId();
Narayan Kamath68e2af52011-11-28 17:10:04 +0000862 if (utteranceId != null) {
Narayan Kamath492b7f02011-11-29 17:02:06 +0000863 mCallbacks.dispatchOnStart(getCallerIdentity(), utteranceId);
Narayan Kamath754c72e2011-11-09 14:22:32 +0000864 }
865 }
866
867 @Override
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +0100868 public void dispatchOnError(int errorCode) {
Narayan Kamath754c72e2011-11-09 14:22:32 +0000869 final String utteranceId = getUtteranceId();
Narayan Kamath68e2af52011-11-28 17:10:04 +0000870 if (utteranceId != null) {
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +0100871 mCallbacks.dispatchOnError(getCallerIdentity(), utteranceId, errorCode);
Narayan Kamath8d1fc242011-06-03 18:11:54 +0100872 }
873 }
874
Niels Egbertsc99ba1c2015-11-20 11:44:25 +0000875 @Override
876 public void dispatchOnBeginSynthesis(int sampleRateInHz, int audioFormat, int channelCount) {
877 final String utteranceId = getUtteranceId();
878 if (utteranceId != null) {
879 mCallbacks.dispatchOnBeginSynthesis(getCallerIdentity(), utteranceId, sampleRateInHz, audioFormat, channelCount);
880 }
881 }
882
883 @Override
884 public void dispatchOnAudioAvailable(byte[] audio) {
885 final String utteranceId = getUtteranceId();
886 if (utteranceId != null) {
887 mCallbacks.dispatchOnAudioAvailable(getCallerIdentity(), utteranceId, audio);
888 }
889 }
890
Niels Egberts65c50782016-12-23 12:01:32 +0000891 @Override
Niels Egberts5d0ea0f2017-03-21 15:29:24 +0000892 public void dispatchOnRangeStart(int start, int end, int frame) {
Niels Egberts65c50782016-12-23 12:01:32 +0000893 final String utteranceId = getUtteranceId();
894 if (utteranceId != null) {
Niels Egberts5d0ea0f2017-03-21 15:29:24 +0000895 mCallbacks.dispatchOnRangeStart(
896 getCallerIdentity(), utteranceId, start, end, frame);
Niels Egberts65c50782016-12-23 12:01:32 +0000897 }
898 }
899
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +0100900 abstract public String getUtteranceId();
901
902 String getStringParam(Bundle params, String key, String defaultValue) {
903 return params == null ? defaultValue : params.getString(key, defaultValue);
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000904 }
905
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +0100906 int getIntParam(Bundle params, String key, int defaultValue) {
907 return params == null ? defaultValue : params.getInt(key, defaultValue);
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000908 }
909
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +0100910 float getFloatParam(Bundle params, String key, float defaultValue) {
911 return params == null ? defaultValue : params.getFloat(key, defaultValue);
912 }
913 }
914
915 /**
Niels Egberts01dedf52016-12-23 15:32:11 +0000916 * Synthesis parameters are kept in a single Bundle passed as parameter. This class allow
917 * subclasses to access them conveniently.
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +0100918 */
Niels Egberts01dedf52016-12-23 15:32:11 +0000919 private abstract class UtteranceSpeechItemWithParams extends UtteranceSpeechItem {
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +0100920 protected final Bundle mParams;
Niels Egbertsdf7deef2014-06-25 15:30:44 +0100921 protected final String mUtteranceId;
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +0100922
Niels Egberts01dedf52016-12-23 15:32:11 +0000923 UtteranceSpeechItemWithParams(
924 Object callerIdentity,
925 int callerUid,
926 int callerPid,
927 Bundle params,
928 String utteranceId) {
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +0100929 super(callerIdentity, callerUid, callerPid);
930 mParams = params;
Niels Egbertsdf7deef2014-06-25 15:30:44 +0100931 mUtteranceId = utteranceId;
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +0100932 }
933
934 boolean hasLanguage() {
935 return !TextUtils.isEmpty(getStringParam(mParams, Engine.KEY_PARAM_LANGUAGE, null));
936 }
937
938 int getSpeechRate() {
939 return getIntParam(mParams, Engine.KEY_PARAM_RATE, getDefaultSpeechRate());
940 }
941
942 int getPitch() {
Sergio Sanchob0cde2c2017-01-26 20:39:05 +0000943 return getIntParam(mParams, Engine.KEY_PARAM_PITCH, getDefaultPitch());
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +0100944 }
945
946 @Override
947 public String getUtteranceId() {
Niels Egbertsdf7deef2014-06-25 15:30:44 +0100948 return mUtteranceId;
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +0100949 }
950
Przemyslaw Szczepaniak5cbf17c2014-06-18 11:35:52 +0100951 AudioOutputParams getAudioParams() {
Niels Egberts01dedf52016-12-23 15:32:11 +0000952 return AudioOutputParams.createFromParamsBundle(mParams, true);
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +0100953 }
954 }
955
Niels Egberts01dedf52016-12-23 15:32:11 +0000956 class SynthesisSpeechItem extends UtteranceSpeechItemWithParams {
Narayan Kamath40f71f02011-11-23 16:42:53 +0000957 // Never null.
Niels Egbertsdf7deef2014-06-25 15:30:44 +0100958 private final CharSequence mText;
Narayan Kamathe22b69a2011-06-08 11:41:47 +0100959 private final SynthesisRequest mSynthesisRequest;
Narayan Kamathe5b8c4d2011-08-22 15:37:47 +0100960 private final String[] mDefaultLocale;
Narayan Kamathe22b69a2011-06-08 11:41:47 +0100961 // Non null after synthesis has started, and all accesses
962 // guarded by 'this'.
963 private AbstractSynthesisCallback mSynthesisCallback;
Niels Egberts01dedf52016-12-23 15:32:11 +0000964 private final EventLogger mEventLogger;
Przemyslaw Szczepaniak65327832013-05-31 13:12:52 +0100965 private final int mCallerUid;
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000966
Niels Egberts01dedf52016-12-23 15:32:11 +0000967 public SynthesisSpeechItem(
968 Object callerIdentity,
969 int callerUid,
970 int callerPid,
971 Bundle params,
972 String utteranceId,
973 CharSequence text) {
Niels Egbertsdf7deef2014-06-25 15:30:44 +0100974 super(callerIdentity, callerUid, callerPid, params, utteranceId);
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000975 mText = text;
Przemyslaw Szczepaniak65327832013-05-31 13:12:52 +0100976 mCallerUid = callerUid;
Narayan Kamathe22b69a2011-06-08 11:41:47 +0100977 mSynthesisRequest = new SynthesisRequest(mText, mParams);
Narayan Kamathe5b8c4d2011-08-22 15:37:47 +0100978 mDefaultLocale = getSettingsLocale();
Narayan Kamathe22b69a2011-06-08 11:41:47 +0100979 setRequestParams(mSynthesisRequest);
Niels Egberts01dedf52016-12-23 15:32:11 +0000980 mEventLogger = new EventLogger(mSynthesisRequest, callerUid, callerPid, mPackageName);
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000981 }
982
Niels Egbertsdf7deef2014-06-25 15:30:44 +0100983 public CharSequence getText() {
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000984 return mText;
985 }
986
987 @Override
988 public boolean isValid() {
Narayan Kamath40f71f02011-11-23 16:42:53 +0000989 if (mText == null) {
Narayan Kamath9c3d7a82012-07-20 18:01:43 +0100990 Log.e(TAG, "null synthesis text");
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000991 return false;
992 }
Przemyslaw Szczepaniak2d940bc2012-11-19 12:22:59 +0000993 if (mText.length() >= TextToSpeech.getMaxSpeechInputLength()) {
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000994 Log.w(TAG, "Text too long: " + mText.length() + " chars");
995 return false;
996 }
997 return true;
998 }
999
1000 @Override
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +01001001 protected void playImpl() {
Narayan Kamathe22b69a2011-06-08 11:41:47 +01001002 AbstractSynthesisCallback synthesisCallback;
Narayan Kamath6dabb632011-07-08 12:13:03 +01001003 mEventLogger.onRequestProcessingStart();
Bjorn Bringert50e657b2011-03-08 16:00:40 +00001004 synchronized (this) {
Narayan Kamatha65c62a2011-09-06 17:04:48 +01001005 // stop() might have been called before we enter this
1006 // synchronized block.
1007 if (isStopped()) {
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +01001008 return;
Narayan Kamatha65c62a2011-09-06 17:04:48 +01001009 }
Narayan Kamathe22b69a2011-06-08 11:41:47 +01001010 mSynthesisCallback = createSynthesisCallback();
1011 synthesisCallback = mSynthesisCallback;
Bjorn Bringert50e657b2011-03-08 16:00:40 +00001012 }
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +01001013
Narayan Kamathe22b69a2011-06-08 11:41:47 +01001014 TextToSpeechService.this.onSynthesizeText(mSynthesisRequest, synthesisCallback);
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +01001015
1016 // Fix for case where client called .start() & .error(), but did not called .done()
1017 if (synthesisCallback.hasStarted() && !synthesisCallback.hasFinished()) {
1018 synthesisCallback.done();
1019 }
Bjorn Bringert50e657b2011-03-08 16:00:40 +00001020 }
1021
Narayan Kamathe22b69a2011-06-08 11:41:47 +01001022 protected AbstractSynthesisCallback createSynthesisCallback() {
Przemyslaw Szczepaniak5cbf17c2014-06-18 11:35:52 +01001023 return new PlaybackSynthesisCallback(getAudioParams(),
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +01001024 mAudioPlaybackHandler, this, getCallerIdentity(), mEventLogger, false);
Bjorn Bringert50e657b2011-03-08 16:00:40 +00001025 }
1026
1027 private void setRequestParams(SynthesisRequest request) {
Przemyslaw Szczepaniakad6df742014-07-01 17:04:25 +01001028 String voiceName = getVoiceName();
Narayan Kamathc3edf2a2011-06-15 12:35:06 +01001029 request.setLanguage(getLanguage(), getCountry(), getVariant());
Przemyslaw Szczepaniakad6df742014-07-01 17:04:25 +01001030 if (!TextUtils.isEmpty(voiceName)) {
1031 request.setVoiceName(getVoiceName());
1032 }
Narayan Kamathc3edf2a2011-06-15 12:35:06 +01001033 request.setSpeechRate(getSpeechRate());
Przemyslaw Szczepaniak65327832013-05-31 13:12:52 +01001034 request.setCallerUid(mCallerUid);
Bjorn Bringert50e657b2011-03-08 16:00:40 +00001035 request.setPitch(getPitch());
1036 }
1037
1038 @Override
1039 protected void stopImpl() {
Narayan Kamathe22b69a2011-06-08 11:41:47 +01001040 AbstractSynthesisCallback synthesisCallback;
Bjorn Bringert50e657b2011-03-08 16:00:40 +00001041 synchronized (this) {
Narayan Kamathe22b69a2011-06-08 11:41:47 +01001042 synthesisCallback = mSynthesisCallback;
Bjorn Bringert50e657b2011-03-08 16:00:40 +00001043 }
Narayan Kamatha65c62a2011-09-06 17:04:48 +01001044 if (synthesisCallback != null) {
1045 // If the synthesis callback is null, it implies that we haven't
1046 // entered the synchronized(this) block in playImpl which in
1047 // turn implies that synthesis would not have started.
1048 synthesisCallback.stop();
1049 TextToSpeechService.this.onStop();
Przemyslaw Szczepaniak4b738672014-11-19 17:36:57 +00001050 } else {
1051 dispatchOnStop();
Narayan Kamatha65c62a2011-09-06 17:04:48 +01001052 }
Bjorn Bringert50e657b2011-03-08 16:00:40 +00001053 }
1054
Bjorn Bringert50e657b2011-03-08 16:00:40 +00001055 private String getCountry() {
Narayan Kamathe5b8c4d2011-08-22 15:37:47 +01001056 if (!hasLanguage()) return mDefaultLocale[1];
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +01001057 return getStringParam(mParams, Engine.KEY_PARAM_COUNTRY, "");
Bjorn Bringert50e657b2011-03-08 16:00:40 +00001058 }
1059
1060 private String getVariant() {
Narayan Kamathe5b8c4d2011-08-22 15:37:47 +01001061 if (!hasLanguage()) return mDefaultLocale[2];
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +01001062 return getStringParam(mParams, Engine.KEY_PARAM_VARIANT, "");
Bjorn Bringert50e657b2011-03-08 16:00:40 +00001063 }
1064
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +01001065 public String getLanguage() {
1066 return getStringParam(mParams, Engine.KEY_PARAM_LANGUAGE, mDefaultLocale[0]);
Bjorn Bringert50e657b2011-03-08 16:00:40 +00001067 }
Przemyslaw Szczepaniakad6df742014-07-01 17:04:25 +01001068
1069 public String getVoiceName() {
1070 return getStringParam(mParams, Engine.KEY_PARAM_VOICE_NAME, "");
1071 }
Bjorn Bringert50e657b2011-03-08 16:00:40 +00001072 }
1073
Niels Egberts01dedf52016-12-23 15:32:11 +00001074 private class SynthesisToFileOutputStreamSpeechItem extends SynthesisSpeechItem {
Przemyslaw Szczepaniakfcf671b2013-03-05 11:28:50 +00001075 private final FileOutputStream mFileOutputStream;
Bjorn Bringert50e657b2011-03-08 16:00:40 +00001076
Niels Egberts01dedf52016-12-23 15:32:11 +00001077 public SynthesisToFileOutputStreamSpeechItem(
1078 Object callerIdentity,
1079 int callerUid,
1080 int callerPid,
1081 Bundle params,
1082 String utteranceId,
1083 CharSequence text,
Niels Egbertsdf7deef2014-06-25 15:30:44 +01001084 FileOutputStream fileOutputStream) {
1085 super(callerIdentity, callerUid, callerPid, params, utteranceId, text);
Przemyslaw Szczepaniakfcf671b2013-03-05 11:28:50 +00001086 mFileOutputStream = fileOutputStream;
Bjorn Bringert50e657b2011-03-08 16:00:40 +00001087 }
1088
1089 @Override
Narayan Kamathe22b69a2011-06-08 11:41:47 +01001090 protected AbstractSynthesisCallback createSynthesisCallback() {
Niels Egberts453c13f2015-11-20 12:05:55 +00001091 return new FileSynthesisCallback(mFileOutputStream.getChannel(), this, false);
Bjorn Bringert50e657b2011-03-08 16:00:40 +00001092 }
1093
Narayan Kamath8d1fc242011-06-03 18:11:54 +01001094 @Override
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +01001095 protected void playImpl() {
Narayan Kamath754c72e2011-11-09 14:22:32 +00001096 dispatchOnStart();
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +01001097 super.playImpl();
Przemyslaw Szczepaniakfcf671b2013-03-05 11:28:50 +00001098 try {
1099 mFileOutputStream.close();
1100 } catch(IOException e) {
1101 Log.w(TAG, "Failed to close output file", e);
1102 }
Narayan Kamath8d1fc242011-06-03 18:11:54 +01001103 }
Bjorn Bringert50e657b2011-03-08 16:00:40 +00001104 }
1105
Niels Egberts01dedf52016-12-23 15:32:11 +00001106 private class AudioSpeechItem extends UtteranceSpeechItemWithParams {
Narayan Kamathaf802c62011-12-05 11:20:07 +00001107 private final AudioPlaybackQueueItem mItem;
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +01001108
Niels Egberts01dedf52016-12-23 15:32:11 +00001109 public AudioSpeechItem(
1110 Object callerIdentity,
1111 int callerUid,
1112 int callerPid,
1113 Bundle params,
1114 String utteranceId,
1115 Uri uri) {
Niels Egbertsdf7deef2014-06-25 15:30:44 +01001116 super(callerIdentity, callerUid, callerPid, params, utteranceId);
Narayan Kamathaf802c62011-12-05 11:20:07 +00001117 mItem = new AudioPlaybackQueueItem(this, getCallerIdentity(),
Przemyslaw Szczepaniak5cbf17c2014-06-18 11:35:52 +01001118 TextToSpeechService.this, uri, getAudioParams());
Bjorn Bringert50e657b2011-03-08 16:00:40 +00001119 }
1120
1121 @Override
1122 public boolean isValid() {
1123 return true;
1124 }
1125
1126 @Override
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +01001127 protected void playImpl() {
Narayan Kamathaf802c62011-12-05 11:20:07 +00001128 mAudioPlaybackHandler.enqueue(mItem);
Bjorn Bringert50e657b2011-03-08 16:00:40 +00001129 }
1130
1131 @Override
1132 protected void stopImpl() {
Narayan Kamathbe4ad4a2011-07-15 13:01:09 +01001133 // Do nothing.
Bjorn Bringert50e657b2011-03-08 16:00:40 +00001134 }
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +01001135
1136 @Override
1137 public String getUtteranceId() {
1138 return getStringParam(mParams, Engine.KEY_PARAM_UTTERANCE_ID, null);
1139 }
Przemyslaw Szczepaniak672695e2014-07-14 17:10:42 +01001140
1141 @Override
1142 AudioOutputParams getAudioParams() {
Niels Egberts01dedf52016-12-23 15:32:11 +00001143 return AudioOutputParams.createFromParamsBundle(mParams, false);
Przemyslaw Szczepaniak672695e2014-07-14 17:10:42 +01001144 }
Bjorn Bringert50e657b2011-03-08 16:00:40 +00001145 }
1146
Przemyslaw Szczepaniak13896b72012-11-09 15:18:16 +00001147 private class SilenceSpeechItem extends UtteranceSpeechItem {
Bjorn Bringert50e657b2011-03-08 16:00:40 +00001148 private final long mDuration;
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +01001149 private final String mUtteranceId;
Bjorn Bringert50e657b2011-03-08 16:00:40 +00001150
Narayan Kamath492b7f02011-11-29 17:02:06 +00001151 public SilenceSpeechItem(Object callerIdentity, int callerUid, int callerPid,
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +01001152 String utteranceId, long duration) {
1153 super(callerIdentity, callerUid, callerPid);
1154 mUtteranceId = utteranceId;
Bjorn Bringert50e657b2011-03-08 16:00:40 +00001155 mDuration = duration;
Bjorn Bringert50e657b2011-03-08 16:00:40 +00001156 }
1157
1158 @Override
1159 public boolean isValid() {
1160 return true;
1161 }
1162
1163 @Override
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +01001164 protected void playImpl() {
Narayan Kamath67ae6bc2011-11-30 14:51:00 +00001165 mAudioPlaybackHandler.enqueue(new SilencePlaybackQueueItem(
1166 this, getCallerIdentity(), mDuration));
Bjorn Bringert50e657b2011-03-08 16:00:40 +00001167 }
1168
1169 @Override
1170 protected void stopImpl() {
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +01001171
1172 }
1173
1174 @Override
1175 public String getUtteranceId() {
1176 return mUtteranceId;
Bjorn Bringert50e657b2011-03-08 16:00:40 +00001177 }
1178 }
1179
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +01001180 /**
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +01001181 * Call {@link TextToSpeechService#onLoadLanguage} on synth thread.
1182 */
Przemyslaw Szczepaniak13896b72012-11-09 15:18:16 +00001183 private class LoadLanguageItem extends SpeechItem {
1184 private final String mLanguage;
1185 private final String mCountry;
1186 private final String mVariant;
1187
1188 public LoadLanguageItem(Object callerIdentity, int callerUid, int callerPid,
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +01001189 String language, String country, String variant) {
1190 super(callerIdentity, callerUid, callerPid);
Przemyslaw Szczepaniak13896b72012-11-09 15:18:16 +00001191 mLanguage = language;
1192 mCountry = country;
1193 mVariant = variant;
1194 }
1195
1196 @Override
1197 public boolean isValid() {
1198 return true;
1199 }
1200
1201 @Override
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +01001202 protected void playImpl() {
1203 TextToSpeechService.this.onLoadLanguage(mLanguage, mCountry, mVariant);
Przemyslaw Szczepaniak13896b72012-11-09 15:18:16 +00001204 }
1205
1206 @Override
1207 protected void stopImpl() {
1208 // No-op
1209 }
1210 }
1211
Przemyslaw Szczepaniakad6df742014-07-01 17:04:25 +01001212 /**
1213 * Call {@link TextToSpeechService#onLoadLanguage} on synth thread.
1214 */
1215 private class LoadVoiceItem extends SpeechItem {
1216 private final String mVoiceName;
1217
1218 public LoadVoiceItem(Object callerIdentity, int callerUid, int callerPid,
1219 String voiceName) {
1220 super(callerIdentity, callerUid, callerPid);
1221 mVoiceName = voiceName;
1222 }
1223
1224 @Override
1225 public boolean isValid() {
1226 return true;
1227 }
1228
1229 @Override
1230 protected void playImpl() {
1231 TextToSpeechService.this.onLoadVoice(mVoiceName);
1232 }
1233
1234 @Override
1235 protected void stopImpl() {
1236 // No-op
1237 }
1238 }
1239
1240
Bjorn Bringert50e657b2011-03-08 16:00:40 +00001241 @Override
1242 public IBinder onBind(Intent intent) {
1243 if (TextToSpeech.Engine.INTENT_ACTION_TTS_SERVICE.equals(intent.getAction())) {
1244 return mBinder;
1245 }
1246 return null;
1247 }
1248
1249 /**
Niels Egberts01dedf52016-12-23 15:32:11 +00001250 * Binder returned from {@code #onBind(Intent)}. The methods in this class can be called called
1251 * from several different threads.
Bjorn Bringert50e657b2011-03-08 16:00:40 +00001252 */
Narayan Kamathabc63fb2011-06-10 11:36:57 +01001253 // NOTE: All calls that are passed in a calling app are interned so that
1254 // they can be used as message objects (which are tested for equality using ==).
Niels Egberts01dedf52016-12-23 15:32:11 +00001255 private final ITextToSpeechService.Stub mBinder =
1256 new ITextToSpeechService.Stub() {
1257 @Override
1258 public int speak(
1259 IBinder caller,
1260 CharSequence text,
1261 int queueMode,
1262 Bundle params,
1263 String utteranceId) {
1264 if (!checkNonNull(caller, text, params)) {
1265 return TextToSpeech.ERROR;
1266 }
Narayan Kamathabc63fb2011-06-10 11:36:57 +01001267
Niels Egberts01dedf52016-12-23 15:32:11 +00001268 SpeechItem item =
1269 new SynthesisSpeechItem(
1270 caller,
1271 Binder.getCallingUid(),
1272 Binder.getCallingPid(),
1273 params,
1274 utteranceId,
1275 text);
1276 return mSynthHandler.enqueueSpeechItem(queueMode, item);
Przemyslaw Szczepaniak13896b72012-11-09 15:18:16 +00001277 }
Bjorn Bringert50e657b2011-03-08 16:00:40 +00001278
Niels Egberts01dedf52016-12-23 15:32:11 +00001279 @Override
1280 public int synthesizeToFileDescriptor(
1281 IBinder caller,
1282 CharSequence text,
1283 ParcelFileDescriptor fileDescriptor,
1284 Bundle params,
1285 String utteranceId) {
1286 if (!checkNonNull(caller, text, fileDescriptor, params)) {
1287 return TextToSpeech.ERROR;
1288 }
Przemyslaw Szczepaniakad6df742014-07-01 17:04:25 +01001289
Niels Egberts01dedf52016-12-23 15:32:11 +00001290 // In test env, ParcelFileDescriptor instance may be EXACTLY the same
1291 // one that is used by client. And it will be closed by a client, thus
1292 // preventing us from writing anything to it.
1293 final ParcelFileDescriptor sameFileDescriptor =
1294 ParcelFileDescriptor.adoptFd(fileDescriptor.detachFd());
Przemyslaw Szczepaniakad6df742014-07-01 17:04:25 +01001295
Niels Egberts01dedf52016-12-23 15:32:11 +00001296 SpeechItem item =
1297 new SynthesisToFileOutputStreamSpeechItem(
1298 caller,
1299 Binder.getCallingUid(),
1300 Binder.getCallingPid(),
1301 params,
1302 utteranceId,
1303 text,
1304 new ParcelFileDescriptor.AutoCloseOutputStream(
1305 sameFileDescriptor));
1306 return mSynthHandler.enqueueSpeechItem(TextToSpeech.QUEUE_ADD, item);
Przemyslaw Szczepaniakad6df742014-07-01 17:04:25 +01001307 }
Przemyslaw Szczepaniakad6df742014-07-01 17:04:25 +01001308
Niels Egberts01dedf52016-12-23 15:32:11 +00001309 @Override
1310 public int playAudio(
1311 IBinder caller,
1312 Uri audioUri,
1313 int queueMode,
1314 Bundle params,
1315 String utteranceId) {
1316 if (!checkNonNull(caller, audioUri, params)) {
1317 return TextToSpeech.ERROR;
1318 }
Przemyslaw Szczepaniakad6df742014-07-01 17:04:25 +01001319
Niels Egberts01dedf52016-12-23 15:32:11 +00001320 SpeechItem item =
1321 new AudioSpeechItem(
1322 caller,
1323 Binder.getCallingUid(),
1324 Binder.getCallingPid(),
1325 params,
1326 utteranceId,
1327 audioUri);
1328 return mSynthHandler.enqueueSpeechItem(queueMode, item);
1329 }
Przemyslaw Szczepaniakad6df742014-07-01 17:04:25 +01001330
Niels Egberts01dedf52016-12-23 15:32:11 +00001331 @Override
1332 public int playSilence(
1333 IBinder caller, long duration, int queueMode, String utteranceId) {
1334 if (!checkNonNull(caller)) {
1335 return TextToSpeech.ERROR;
1336 }
Narayan Kamathabc63fb2011-06-10 11:36:57 +01001337
Niels Egberts01dedf52016-12-23 15:32:11 +00001338 SpeechItem item =
1339 new SilenceSpeechItem(
1340 caller,
1341 Binder.getCallingUid(),
1342 Binder.getCallingPid(),
1343 utteranceId,
1344 duration);
1345 return mSynthHandler.enqueueSpeechItem(queueMode, item);
1346 }
Narayan Kamath7a3af862011-06-02 17:28:57 +01001347
Niels Egberts01dedf52016-12-23 15:32:11 +00001348 @Override
1349 public boolean isSpeaking() {
1350 return mSynthHandler.isSpeaking() || mAudioPlaybackHandler.isSpeaking();
1351 }
Narayan Kamathabc63fb2011-06-10 11:36:57 +01001352
Niels Egberts01dedf52016-12-23 15:32:11 +00001353 @Override
1354 public int stop(IBinder caller) {
1355 if (!checkNonNull(caller)) {
1356 return TextToSpeech.ERROR;
1357 }
1358
1359 return mSynthHandler.stopForApp(caller);
1360 }
1361
1362 @Override
1363 public String[] getLanguage() {
1364 return onGetLanguage();
1365 }
1366
1367 @Override
1368 public String[] getClientDefaultLanguage() {
1369 return getSettingsLocale();
1370 }
1371
1372 /*
1373 * If defaults are enforced, then no language is "available" except
1374 * perhaps the default language selected by the user.
1375 */
1376 @Override
1377 public int isLanguageAvailable(String lang, String country, String variant) {
1378 if (!checkNonNull(lang)) {
1379 return TextToSpeech.ERROR;
1380 }
1381
1382 return onIsLanguageAvailable(lang, country, variant);
1383 }
1384
1385 @Override
1386 public String[] getFeaturesForLanguage(
1387 String lang, String country, String variant) {
1388 Set<String> features = onGetFeaturesForLanguage(lang, country, variant);
1389 String[] featuresArray = null;
1390 if (features != null) {
1391 featuresArray = new String[features.size()];
1392 features.toArray(featuresArray);
1393 } else {
1394 featuresArray = new String[0];
1395 }
1396 return featuresArray;
1397 }
1398
1399 /*
1400 * There is no point loading a non default language if defaults
1401 * are enforced.
1402 */
1403 @Override
1404 public int loadLanguage(
1405 IBinder caller, String lang, String country, String variant) {
1406 if (!checkNonNull(lang)) {
1407 return TextToSpeech.ERROR;
1408 }
1409 int retVal = onIsLanguageAvailable(lang, country, variant);
1410
1411 if (retVal == TextToSpeech.LANG_AVAILABLE
1412 || retVal == TextToSpeech.LANG_COUNTRY_AVAILABLE
1413 || retVal == TextToSpeech.LANG_COUNTRY_VAR_AVAILABLE) {
1414
1415 SpeechItem item =
1416 new LoadLanguageItem(
1417 caller,
1418 Binder.getCallingUid(),
1419 Binder.getCallingPid(),
1420 lang,
1421 country,
1422 variant);
1423
1424 if (mSynthHandler.enqueueSpeechItem(TextToSpeech.QUEUE_ADD, item)
1425 != TextToSpeech.SUCCESS) {
1426 return TextToSpeech.ERROR;
1427 }
1428 }
1429 return retVal;
1430 }
1431
1432 @Override
1433 public List<Voice> getVoices() {
1434 return onGetVoices();
1435 }
1436
1437 @Override
1438 public int loadVoice(IBinder caller, String voiceName) {
1439 if (!checkNonNull(voiceName)) {
1440 return TextToSpeech.ERROR;
1441 }
1442 int retVal = onIsValidVoiceName(voiceName);
1443
1444 if (retVal == TextToSpeech.SUCCESS) {
1445 SpeechItem item =
1446 new LoadVoiceItem(
1447 caller,
1448 Binder.getCallingUid(),
1449 Binder.getCallingPid(),
1450 voiceName);
1451 if (mSynthHandler.enqueueSpeechItem(TextToSpeech.QUEUE_ADD, item)
1452 != TextToSpeech.SUCCESS) {
1453 return TextToSpeech.ERROR;
1454 }
1455 }
1456 return retVal;
1457 }
1458
1459 public String getDefaultVoiceNameFor(String lang, String country, String variant) {
1460 if (!checkNonNull(lang)) {
1461 return null;
1462 }
1463 int retVal = onIsLanguageAvailable(lang, country, variant);
1464
1465 if (retVal == TextToSpeech.LANG_AVAILABLE
1466 || retVal == TextToSpeech.LANG_COUNTRY_AVAILABLE
1467 || retVal == TextToSpeech.LANG_COUNTRY_VAR_AVAILABLE) {
1468 return onGetDefaultVoiceNameFor(lang, country, variant);
1469 } else {
1470 return null;
1471 }
1472 }
1473
1474 @Override
1475 public void setCallback(IBinder caller, ITextToSpeechCallback cb) {
1476 // Note that passing in a null callback is a valid use case.
1477 if (!checkNonNull(caller)) {
1478 return;
1479 }
1480
1481 mCallbacks.setCallback(caller, cb);
1482 }
1483
1484 private String intern(String in) {
1485 // The input parameter will be non null.
1486 return in.intern();
1487 }
1488
1489 private boolean checkNonNull(Object... args) {
1490 for (Object o : args) {
1491 if (o == null) return false;
1492 }
1493 return true;
1494 }
1495 };
Bjorn Bringert50e657b2011-03-08 16:00:40 +00001496
1497 private class CallbackMap extends RemoteCallbackList<ITextToSpeechCallback> {
Narayan Kamath492b7f02011-11-29 17:02:06 +00001498 private final HashMap<IBinder, ITextToSpeechCallback> mCallerToCallback
1499 = new HashMap<IBinder, ITextToSpeechCallback>();
Bjorn Bringert50e657b2011-03-08 16:00:40 +00001500
Narayan Kamath492b7f02011-11-29 17:02:06 +00001501 public void setCallback(IBinder caller, ITextToSpeechCallback cb) {
1502 synchronized (mCallerToCallback) {
Bjorn Bringert50e657b2011-03-08 16:00:40 +00001503 ITextToSpeechCallback old;
1504 if (cb != null) {
Narayan Kamath492b7f02011-11-29 17:02:06 +00001505 register(cb, caller);
1506 old = mCallerToCallback.put(caller, cb);
Bjorn Bringert50e657b2011-03-08 16:00:40 +00001507 } else {
Narayan Kamath492b7f02011-11-29 17:02:06 +00001508 old = mCallerToCallback.remove(caller);
Bjorn Bringert50e657b2011-03-08 16:00:40 +00001509 }
1510 if (old != null && old != cb) {
1511 unregister(old);
1512 }
1513 }
1514 }
1515
Przemyslaw Szczepaniak4b738672014-11-19 17:36:57 +00001516 public void dispatchOnStop(Object callerIdentity, String utteranceId, boolean started) {
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +01001517 ITextToSpeechCallback cb = getCallbackFor(callerIdentity);
1518 if (cb == null) return;
1519 try {
Przemyslaw Szczepaniak4b738672014-11-19 17:36:57 +00001520 cb.onStop(utteranceId, started);
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +01001521 } catch (RemoteException e) {
1522 Log.e(TAG, "Callback onStop failed: " + e);
1523 }
1524 }
1525
1526 public void dispatchOnSuccess(Object callerIdentity, String utteranceId) {
1527 ITextToSpeechCallback cb = getCallbackFor(callerIdentity);
1528 if (cb == null) return;
1529 try {
1530 cb.onSuccess(utteranceId);
Bjorn Bringert50e657b2011-03-08 16:00:40 +00001531 } catch (RemoteException e) {
Narayan Kamath754c72e2011-11-09 14:22:32 +00001532 Log.e(TAG, "Callback onDone failed: " + e);
1533 }
1534 }
1535
Narayan Kamath492b7f02011-11-29 17:02:06 +00001536 public void dispatchOnStart(Object callerIdentity, String utteranceId) {
1537 ITextToSpeechCallback cb = getCallbackFor(callerIdentity);
Narayan Kamath754c72e2011-11-09 14:22:32 +00001538 if (cb == null) return;
1539 try {
1540 cb.onStart(utteranceId);
1541 } catch (RemoteException e) {
1542 Log.e(TAG, "Callback onStart failed: " + e);
1543 }
Narayan Kamath754c72e2011-11-09 14:22:32 +00001544 }
1545
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +01001546 public void dispatchOnError(Object callerIdentity, String utteranceId,
1547 int errorCode) {
Narayan Kamath492b7f02011-11-29 17:02:06 +00001548 ITextToSpeechCallback cb = getCallbackFor(callerIdentity);
Narayan Kamath754c72e2011-11-09 14:22:32 +00001549 if (cb == null) return;
1550 try {
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +01001551 cb.onError(utteranceId, errorCode);
Narayan Kamath754c72e2011-11-09 14:22:32 +00001552 } catch (RemoteException e) {
1553 Log.e(TAG, "Callback onError failed: " + e);
Bjorn Bringert50e657b2011-03-08 16:00:40 +00001554 }
1555 }
1556
Niels Egbertsc99ba1c2015-11-20 11:44:25 +00001557 public void dispatchOnBeginSynthesis(Object callerIdentity, String utteranceId, int sampleRateInHz, int audioFormat, int channelCount) {
1558 ITextToSpeechCallback cb = getCallbackFor(callerIdentity);
1559 if (cb == null) return;
1560 try {
1561 cb.onBeginSynthesis(utteranceId, sampleRateInHz, audioFormat, channelCount);
1562 } catch (RemoteException e) {
1563 Log.e(TAG, "Callback dispatchOnBeginSynthesis(String, int, int, int) failed: " + e);
1564 }
1565 }
1566
1567 public void dispatchOnAudioAvailable(Object callerIdentity, String utteranceId, byte[] buffer) {
1568 ITextToSpeechCallback cb = getCallbackFor(callerIdentity);
1569 if (cb == null) return;
1570 try {
1571 cb.onAudioAvailable(utteranceId, buffer);
1572 } catch (RemoteException e) {
1573 Log.e(TAG, "Callback dispatchOnAudioAvailable(String, byte[]) failed: " + e);
1574 }
1575 }
1576
Niels Egberts5d0ea0f2017-03-21 15:29:24 +00001577 public void dispatchOnRangeStart(
1578 Object callerIdentity, String utteranceId, int start, int end, int frame) {
Niels Egberts65c50782016-12-23 12:01:32 +00001579 ITextToSpeechCallback cb = getCallbackFor(callerIdentity);
1580 if (cb == null) return;
1581 try {
Niels Egberts5d0ea0f2017-03-21 15:29:24 +00001582 cb.onRangeStart(utteranceId, start, end, frame);
Niels Egberts65c50782016-12-23 12:01:32 +00001583 } catch (RemoteException e) {
Niels Egberts5d0ea0f2017-03-21 15:29:24 +00001584 Log.e(TAG, "Callback dispatchOnRangeStart(String, int, int, int) failed: " + e);
Niels Egberts65c50782016-12-23 12:01:32 +00001585 }
1586 }
1587
Bjorn Bringert50e657b2011-03-08 16:00:40 +00001588 @Override
1589 public void onCallbackDied(ITextToSpeechCallback callback, Object cookie) {
Narayan Kamath492b7f02011-11-29 17:02:06 +00001590 IBinder caller = (IBinder) cookie;
1591 synchronized (mCallerToCallback) {
1592 mCallerToCallback.remove(caller);
Bjorn Bringert50e657b2011-03-08 16:00:40 +00001593 }
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +01001594 //mSynthHandler.stopForApp(caller);
Bjorn Bringert50e657b2011-03-08 16:00:40 +00001595 }
1596
1597 @Override
1598 public void kill() {
Narayan Kamath492b7f02011-11-29 17:02:06 +00001599 synchronized (mCallerToCallback) {
1600 mCallerToCallback.clear();
Bjorn Bringert50e657b2011-03-08 16:00:40 +00001601 super.kill();
1602 }
1603 }
1604
Narayan Kamath492b7f02011-11-29 17:02:06 +00001605 private ITextToSpeechCallback getCallbackFor(Object caller) {
Narayan Kamath754c72e2011-11-09 14:22:32 +00001606 ITextToSpeechCallback cb;
Narayan Kamath492b7f02011-11-29 17:02:06 +00001607 IBinder asBinder = (IBinder) caller;
1608 synchronized (mCallerToCallback) {
1609 cb = mCallerToCallback.get(asBinder);
Narayan Kamath754c72e2011-11-09 14:22:32 +00001610 }
1611
1612 return cb;
1613 }
Bjorn Bringert50e657b2011-03-08 16:00:40 +00001614 }
Bjorn Bringert50e657b2011-03-08 16:00:40 +00001615}