blob: 10d7911316aca49deb8c445b2fd1369f7d489db6 [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
Kazuhiro Inaba35e6bb42017-07-18 14:51:05 +0900505 private synchronized boolean setCurrentSpeechItem(SpeechItem speechItem) {
506 // Do not set as current if the item has already been flushed. The check is
507 // intentionally put inside this synchronized method. Specifically, the following
508 // racy sequence between this method and stopForApp() needs to be avoided.
509 // (this method) (stopForApp)
510 // 1. isFlushed
511 // 2. startFlushingSpeechItems
512 // 3. maybeRemoveCurrentSpeechItem
513 // 4. set mCurrentSpeechItem
514 // If it happens, stop() is never called on the item. The guard by synchornized(this)
515 // ensures that the step 3 cannot interrupt between 1 and 4.
516 if (speechItem != null && isFlushed(speechItem)) {
517 return false;
518 }
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000519 mCurrentSpeechItem = speechItem;
Kazuhiro Inaba35e6bb42017-07-18 14:51:05 +0900520 return true;
521 }
522
523 private synchronized SpeechItem removeCurrentSpeechItem() {
524 SpeechItem current = mCurrentSpeechItem;
525 mCurrentSpeechItem = null;
526 return current;
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000527 }
528
Narayan Kamath492b7f02011-11-29 17:02:06 +0000529 private synchronized SpeechItem maybeRemoveCurrentSpeechItem(Object callerIdentity) {
Narayan Kamatha65c62a2011-09-06 17:04:48 +0100530 if (mCurrentSpeechItem != null &&
Przemyslaw Szczepaniak13896b72012-11-09 15:18:16 +0000531 (mCurrentSpeechItem.getCallerIdentity() == callerIdentity)) {
Narayan Kamatha65c62a2011-09-06 17:04:48 +0100532 SpeechItem current = mCurrentSpeechItem;
533 mCurrentSpeechItem = null;
534 return current;
535 }
536
537 return null;
538 }
539
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000540 public boolean isSpeaking() {
541 return getCurrentSpeechItem() != null;
542 }
543
544 public void quit() {
545 // Don't process any more speech items
546 getLooper().quit();
547 // Stop the current speech item
Kazuhiro Inaba35e6bb42017-07-18 14:51:05 +0900548 SpeechItem current = removeCurrentSpeechItem();
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000549 if (current != null) {
550 current.stop();
551 }
Narayan Kamathbe4ad4a2011-07-15 13:01:09 +0100552 // The AudioPlaybackHandler will be destroyed by the caller.
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000553 }
554
555 /**
556 * Adds a speech item to the queue.
557 *
558 * Called on a service binder thread.
559 */
560 public int enqueueSpeechItem(int queueMode, final SpeechItem speechItem) {
Przemyslaw Szczepaniak13896b72012-11-09 15:18:16 +0000561 UtteranceProgressDispatcher utterenceProgress = null;
562 if (speechItem instanceof UtteranceProgressDispatcher) {
563 utterenceProgress = (UtteranceProgressDispatcher) speechItem;
564 }
565
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000566 if (!speechItem.isValid()) {
Przemyslaw Szczepaniak13896b72012-11-09 15:18:16 +0000567 if (utterenceProgress != null) {
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +0100568 utterenceProgress.dispatchOnError(
Przemyslaw Szczepaniakfc4b2892014-06-26 11:52:20 +0100569 TextToSpeech.ERROR_INVALID_REQUEST);
Przemyslaw Szczepaniak13896b72012-11-09 15:18:16 +0000570 }
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000571 return TextToSpeech.ERROR;
572 }
Narayan Kamath4924fe32011-06-09 11:35:13 +0100573
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000574 if (queueMode == TextToSpeech.QUEUE_FLUSH) {
Narayan Kamath492b7f02011-11-29 17:02:06 +0000575 stopForApp(speechItem.getCallerIdentity());
Narayan Kamathabc63fb2011-06-10 11:36:57 +0100576 } else if (queueMode == TextToSpeech.QUEUE_DESTROY) {
Narayan Kamatha65c62a2011-09-06 17:04:48 +0100577 stopAll();
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000578 }
579 Runnable runnable = new Runnable() {
580 @Override
581 public void run() {
Kazuhiro Inaba35e6bb42017-07-18 14:51:05 +0900582 if (setCurrentSpeechItem(speechItem)) {
Przemyslaw Szczepaniak4b738672014-11-19 17:36:57 +0000583 speechItem.play();
Kazuhiro Inaba35e6bb42017-07-18 14:51:05 +0900584 removeCurrentSpeechItem();
585 } else {
586 // The item is alreadly flushed. Stopping.
587 speechItem.stop();
Przemyslaw Szczepaniak4b738672014-11-19 17:36:57 +0000588 }
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000589 }
590 };
591 Message msg = Message.obtain(this, runnable);
Przemyslaw Szczepaniak13896b72012-11-09 15:18:16 +0000592
Narayan Kamatha65c62a2011-09-06 17:04:48 +0100593 // The obj is used to remove all callbacks from the given app in
594 // stopForApp(String).
Narayan Kamathabc63fb2011-06-10 11:36:57 +0100595 //
596 // Note that this string is interned, so the == comparison works.
Narayan Kamath492b7f02011-11-29 17:02:06 +0000597 msg.obj = speechItem.getCallerIdentity();
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +0100598
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000599 if (sendMessage(msg)) {
600 return TextToSpeech.SUCCESS;
601 } else {
602 Log.w(TAG, "SynthThread has quit");
Przemyslaw Szczepaniak13896b72012-11-09 15:18:16 +0000603 if (utterenceProgress != null) {
Przemyslaw Szczepaniakfc4b2892014-06-26 11:52:20 +0100604 utterenceProgress.dispatchOnError(TextToSpeech.ERROR_SERVICE);
Przemyslaw Szczepaniak13896b72012-11-09 15:18:16 +0000605 }
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000606 return TextToSpeech.ERROR;
607 }
608 }
609
610 /**
611 * Stops all speech output and removes any utterances still in the queue for
612 * the calling app.
613 *
614 * Called on a service binder thread.
615 */
Przemyslaw Szczepaniak4b738672014-11-19 17:36:57 +0000616 public int stopForApp(final Object callerIdentity) {
Narayan Kamath492b7f02011-11-29 17:02:06 +0000617 if (callerIdentity == null) {
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000618 return TextToSpeech.ERROR;
619 }
Narayan Kamath4924fe32011-06-09 11:35:13 +0100620
Kazuhiro Inaba35e6bb42017-07-18 14:51:05 +0900621 // Flush pending messages from callerIdentity.
622 // See setCurrentSpeechItem on a subtlety around a race condition.
Przemyslaw Szczepaniak4b738672014-11-19 17:36:57 +0000623 startFlushingSpeechItems(callerIdentity);
624
Narayan Kamathbe4ad4a2011-07-15 13:01:09 +0100625 // This stops writing data to the file / or publishing
626 // items to the audio playback handler.
Narayan Kamatha65c62a2011-09-06 17:04:48 +0100627 //
628 // Note that the current speech item must be removed only if it
629 // belongs to the callingApp, else the item will be "orphaned" and
630 // not stopped correctly if a stop request comes along for the item
631 // from the app it belongs to.
Narayan Kamath492b7f02011-11-29 17:02:06 +0000632 SpeechItem current = maybeRemoveCurrentSpeechItem(callerIdentity);
Narayan Kamatha65c62a2011-09-06 17:04:48 +0100633 if (current != null) {
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000634 current.stop();
635 }
Narayan Kamath8d1fc242011-06-03 18:11:54 +0100636
Narayan Kamath4924fe32011-06-09 11:35:13 +0100637 // Remove any enqueued audio too.
Narayan Kamath67ae6bc2011-11-30 14:51:00 +0000638 mAudioPlaybackHandler.stopForApp(callerIdentity);
Narayan Kamath4924fe32011-06-09 11:35:13 +0100639
Przemyslaw Szczepaniak4b738672014-11-19 17:36:57 +0000640 // Stop flushing pending messages
641 Runnable runnable = new Runnable() {
642 @Override
643 public void run() {
644 endFlushingSpeechItems(callerIdentity);
645 }
646 };
647 sendMessage(Message.obtain(this, runnable));
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000648 return TextToSpeech.SUCCESS;
649 }
Narayan Kamatha65c62a2011-09-06 17:04:48 +0100650
651 public int stopAll() {
Przemyslaw Szczepaniak4b738672014-11-19 17:36:57 +0000652 // Order to flush pending messages
653 startFlushingSpeechItems(null);
654
Przemyslaw Szczepaniak13896b72012-11-09 15:18:16 +0000655 // Stop the current speech item unconditionally .
Kazuhiro Inaba35e6bb42017-07-18 14:51:05 +0900656 SpeechItem current = removeCurrentSpeechItem();
Narayan Kamatha65c62a2011-09-06 17:04:48 +0100657 if (current != null) {
658 current.stop();
659 }
Narayan Kamatha65c62a2011-09-06 17:04:48 +0100660 // Remove all pending playback as well.
Narayan Kamath67ae6bc2011-11-30 14:51:00 +0000661 mAudioPlaybackHandler.stop();
Narayan Kamatha65c62a2011-09-06 17:04:48 +0100662
Przemyslaw Szczepaniak4b738672014-11-19 17:36:57 +0000663 // Message to stop flushing pending messages
664 Runnable runnable = new Runnable() {
665 @Override
666 public void run() {
667 endFlushingSpeechItems(null);
668 }
669 };
670 sendMessage(Message.obtain(this, runnable));
671
672
Narayan Kamatha65c62a2011-09-06 17:04:48 +0100673 return TextToSpeech.SUCCESS;
674 }
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000675 }
676
Narayan Kamath754c72e2011-11-09 14:22:32 +0000677 interface UtteranceProgressDispatcher {
Niels Egberts2c80a0382016-12-23 12:18:22 +0000678 void dispatchOnStop();
679
680 void dispatchOnSuccess();
681
682 void dispatchOnStart();
683
684 void dispatchOnError(int errorCode);
685
686 void dispatchOnBeginSynthesis(int sampleRateInHz, int audioFormat, int channelCount);
687
688 void dispatchOnAudioAvailable(byte[] audio);
Niels Egberts65c50782016-12-23 12:01:32 +0000689
Niels Egberts5d0ea0f2017-03-21 15:29:24 +0000690 public void dispatchOnRangeStart(int start, int end, int frame);
Narayan Kamath8d1fc242011-06-03 18:11:54 +0100691 }
692
Przemyslaw Szczepaniak5cbf17c2014-06-18 11:35:52 +0100693 /** Set of parameters affecting audio output. */
694 static class AudioOutputParams {
695 /**
696 * Audio session identifier. May be used to associate audio playback with one of the
697 * {@link android.media.audiofx.AudioEffect} objects. If not specified by client,
Glenn Kasten33b84042016-03-08 12:02:55 -0800698 * it should be equal to {@link AudioManager#AUDIO_SESSION_ID_GENERATE}.
Przemyslaw Szczepaniak5cbf17c2014-06-18 11:35:52 +0100699 */
700 public final int mSessionId;
701
702 /**
Przemyslaw Szczepaniak5cbf17c2014-06-18 11:35:52 +0100703 * Volume, in the range [0.0f, 1.0f]. The default value is
704 * {@link TextToSpeech.Engine#DEFAULT_VOLUME} (1.0f).
705 */
706 public final float mVolume;
707
708 /**
709 * Left/right position of the audio, in the range [-1.0f, 1.0f].
710 * The default value is {@link TextToSpeech.Engine#DEFAULT_PAN} (0.0f).
711 */
712 public final float mPan;
713
Przemyslaw Szczepaniak672695e2014-07-14 17:10:42 +0100714
715 /**
716 * Audio attributes, set by {@link TextToSpeech#setAudioAttributes}
717 * or created from the value of {@link TextToSpeech.Engine#KEY_PARAM_STREAM}.
718 */
719 public final AudioAttributes mAudioAttributes;
720
Przemyslaw Szczepaniak5cbf17c2014-06-18 11:35:52 +0100721 /** Create AudioOutputParams with default values */
722 AudioOutputParams() {
Glenn Kasten33b84042016-03-08 12:02:55 -0800723 mSessionId = AudioManager.AUDIO_SESSION_ID_GENERATE;
Przemyslaw Szczepaniak5cbf17c2014-06-18 11:35:52 +0100724 mVolume = Engine.DEFAULT_VOLUME;
725 mPan = Engine.DEFAULT_PAN;
Przemyslaw Szczepaniak672695e2014-07-14 17:10:42 +0100726 mAudioAttributes = null;
Przemyslaw Szczepaniak5cbf17c2014-06-18 11:35:52 +0100727 }
728
Przemyslaw Szczepaniak672695e2014-07-14 17:10:42 +0100729 AudioOutputParams(int sessionId, float volume, float pan,
730 AudioAttributes audioAttributes) {
Przemyslaw Szczepaniak5cbf17c2014-06-18 11:35:52 +0100731 mSessionId = sessionId;
Przemyslaw Szczepaniak5cbf17c2014-06-18 11:35:52 +0100732 mVolume = volume;
733 mPan = pan;
Przemyslaw Szczepaniak672695e2014-07-14 17:10:42 +0100734 mAudioAttributes = audioAttributes;
Przemyslaw Szczepaniak5cbf17c2014-06-18 11:35:52 +0100735 }
736
737 /** Create AudioOutputParams from A {@link SynthesisRequest#getParams()} bundle */
Niels Egberts01dedf52016-12-23 15:32:11 +0000738 static AudioOutputParams createFromParamsBundle(Bundle paramsBundle, boolean isSpeech) {
Przemyslaw Szczepaniak5cbf17c2014-06-18 11:35:52 +0100739 if (paramsBundle == null) {
740 return new AudioOutputParams();
741 }
742
Przemyslaw Szczepaniak672695e2014-07-14 17:10:42 +0100743 AudioAttributes audioAttributes =
744 (AudioAttributes) paramsBundle.getParcelable(
745 Engine.KEY_PARAM_AUDIO_ATTRIBUTES);
746 if (audioAttributes == null) {
747 int streamType = paramsBundle.getInt(
748 Engine.KEY_PARAM_STREAM, Engine.DEFAULT_STREAM);
749 audioAttributes = (new AudioAttributes.Builder())
750 .setLegacyStreamType(streamType)
751 .setContentType((isSpeech ?
752 AudioAttributes.CONTENT_TYPE_SPEECH :
753 AudioAttributes.CONTENT_TYPE_SONIFICATION))
754 .build();
755 }
756
Przemyslaw Szczepaniak5cbf17c2014-06-18 11:35:52 +0100757 return new AudioOutputParams(
758 paramsBundle.getInt(
759 Engine.KEY_PARAM_SESSION_ID,
Glenn Kasten33b84042016-03-08 12:02:55 -0800760 AudioManager.AUDIO_SESSION_ID_GENERATE),
Przemyslaw Szczepaniak5cbf17c2014-06-18 11:35:52 +0100761 paramsBundle.getFloat(
762 Engine.KEY_PARAM_VOLUME,
763 Engine.DEFAULT_VOLUME),
764 paramsBundle.getFloat(
765 Engine.KEY_PARAM_PAN,
Przemyslaw Szczepaniak672695e2014-07-14 17:10:42 +0100766 Engine.DEFAULT_PAN),
767 audioAttributes);
Przemyslaw Szczepaniak5cbf17c2014-06-18 11:35:52 +0100768 }
Przemyslaw Szczepaniak5cbf17c2014-06-18 11:35:52 +0100769 }
770
771
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000772 /**
773 * An item in the synth thread queue.
774 */
Przemyslaw Szczepaniak13896b72012-11-09 15:18:16 +0000775 private abstract class SpeechItem {
Narayan Kamath492b7f02011-11-29 17:02:06 +0000776 private final Object mCallerIdentity;
Narayan Kamath492b7f02011-11-29 17:02:06 +0000777 private final int mCallerUid;
778 private final int mCallerPid;
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000779 private boolean mStarted = false;
780 private boolean mStopped = false;
781
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +0100782 public SpeechItem(Object caller, int callerUid, int callerPid) {
Narayan Kamath492b7f02011-11-29 17:02:06 +0000783 mCallerIdentity = caller;
Narayan Kamath492b7f02011-11-29 17:02:06 +0000784 mCallerUid = callerUid;
785 mCallerPid = callerPid;
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000786 }
787
Narayan Kamath492b7f02011-11-29 17:02:06 +0000788 public Object getCallerIdentity() {
789 return mCallerIdentity;
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000790 }
791
Przemyslaw Szczepaniak13896b72012-11-09 15:18:16 +0000792 public int getCallerUid() {
793 return mCallerUid;
794 }
795
796 public int getCallerPid() {
797 return mCallerPid;
798 }
799
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000800 /**
801 * Checker whether the item is valid. If this method returns false, the item should not
802 * be played.
803 */
804 public abstract boolean isValid();
805
806 /**
807 * Plays the speech item. Blocks until playback is finished.
808 * Must not be called more than once.
809 *
810 * Only called on the synthesis thread.
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000811 */
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +0100812 public void play() {
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000813 synchronized (this) {
814 if (mStarted) {
815 throw new IllegalStateException("play() called twice");
816 }
817 mStarted = true;
818 }
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +0100819 playImpl();
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000820 }
821
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +0100822 protected abstract void playImpl();
Przemyslaw Szczepaniak13896b72012-11-09 15:18:16 +0000823
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000824 /**
825 * Stops the speech item.
826 * Must not be called more than once.
827 *
828 * Can be called on multiple threads, but not on the synthesis thread.
829 */
830 public void stop() {
831 synchronized (this) {
832 if (mStopped) {
833 throw new IllegalStateException("stop() called twice");
834 }
835 mStopped = true;
836 }
837 stopImpl();
838 }
839
Przemyslaw Szczepaniak13896b72012-11-09 15:18:16 +0000840 protected abstract void stopImpl();
841
842 protected synchronized boolean isStopped() {
843 return mStopped;
844 }
Przemyslaw Szczepaniak4b738672014-11-19 17:36:57 +0000845
846 protected synchronized boolean isStarted() {
847 return mStarted;
848 }
Przemyslaw Szczepaniak13896b72012-11-09 15:18:16 +0000849 }
850
851 /**
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +0100852 * An item in the synth thread queue that process utterance (and call back to client about
853 * progress).
Przemyslaw Szczepaniak13896b72012-11-09 15:18:16 +0000854 */
855 private abstract class UtteranceSpeechItem extends SpeechItem
856 implements UtteranceProgressDispatcher {
857
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +0100858 public UtteranceSpeechItem(Object caller, int callerUid, int callerPid) {
859 super(caller, callerUid, callerPid);
Przemyslaw Szczepaniak13896b72012-11-09 15:18:16 +0000860 }
861
Narayan Kamath754c72e2011-11-09 14:22:32 +0000862 @Override
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +0100863 public void dispatchOnSuccess() {
Narayan Kamath8d1fc242011-06-03 18:11:54 +0100864 final String utteranceId = getUtteranceId();
Narayan Kamath68e2af52011-11-28 17:10:04 +0000865 if (utteranceId != null) {
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +0100866 mCallbacks.dispatchOnSuccess(getCallerIdentity(), utteranceId);
867 }
868 }
869
870 @Override
871 public void dispatchOnStop() {
872 final String utteranceId = getUtteranceId();
873 if (utteranceId != null) {
Przemyslaw Szczepaniak4b738672014-11-19 17:36:57 +0000874 mCallbacks.dispatchOnStop(getCallerIdentity(), utteranceId, isStarted());
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +0100875 }
876 }
877
878 @Override
Narayan Kamath754c72e2011-11-09 14:22:32 +0000879 public void dispatchOnStart() {
880 final String utteranceId = getUtteranceId();
Narayan Kamath68e2af52011-11-28 17:10:04 +0000881 if (utteranceId != null) {
Narayan Kamath492b7f02011-11-29 17:02:06 +0000882 mCallbacks.dispatchOnStart(getCallerIdentity(), utteranceId);
Narayan Kamath754c72e2011-11-09 14:22:32 +0000883 }
884 }
885
886 @Override
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +0100887 public void dispatchOnError(int errorCode) {
Narayan Kamath754c72e2011-11-09 14:22:32 +0000888 final String utteranceId = getUtteranceId();
Narayan Kamath68e2af52011-11-28 17:10:04 +0000889 if (utteranceId != null) {
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +0100890 mCallbacks.dispatchOnError(getCallerIdentity(), utteranceId, errorCode);
Narayan Kamath8d1fc242011-06-03 18:11:54 +0100891 }
892 }
893
Niels Egbertsc99ba1c2015-11-20 11:44:25 +0000894 @Override
895 public void dispatchOnBeginSynthesis(int sampleRateInHz, int audioFormat, int channelCount) {
896 final String utteranceId = getUtteranceId();
897 if (utteranceId != null) {
898 mCallbacks.dispatchOnBeginSynthesis(getCallerIdentity(), utteranceId, sampleRateInHz, audioFormat, channelCount);
899 }
900 }
901
902 @Override
903 public void dispatchOnAudioAvailable(byte[] audio) {
904 final String utteranceId = getUtteranceId();
905 if (utteranceId != null) {
906 mCallbacks.dispatchOnAudioAvailable(getCallerIdentity(), utteranceId, audio);
907 }
908 }
909
Niels Egberts65c50782016-12-23 12:01:32 +0000910 @Override
Niels Egberts5d0ea0f2017-03-21 15:29:24 +0000911 public void dispatchOnRangeStart(int start, int end, int frame) {
Niels Egberts65c50782016-12-23 12:01:32 +0000912 final String utteranceId = getUtteranceId();
913 if (utteranceId != null) {
Niels Egberts5d0ea0f2017-03-21 15:29:24 +0000914 mCallbacks.dispatchOnRangeStart(
915 getCallerIdentity(), utteranceId, start, end, frame);
Niels Egberts65c50782016-12-23 12:01:32 +0000916 }
917 }
918
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +0100919 abstract public String getUtteranceId();
920
921 String getStringParam(Bundle params, String key, String defaultValue) {
922 return params == null ? defaultValue : params.getString(key, defaultValue);
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000923 }
924
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +0100925 int getIntParam(Bundle params, String key, int defaultValue) {
926 return params == null ? defaultValue : params.getInt(key, defaultValue);
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000927 }
928
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +0100929 float getFloatParam(Bundle params, String key, float defaultValue) {
930 return params == null ? defaultValue : params.getFloat(key, defaultValue);
931 }
932 }
933
934 /**
Niels Egberts01dedf52016-12-23 15:32:11 +0000935 * Synthesis parameters are kept in a single Bundle passed as parameter. This class allow
936 * subclasses to access them conveniently.
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +0100937 */
Niels Egberts01dedf52016-12-23 15:32:11 +0000938 private abstract class UtteranceSpeechItemWithParams extends UtteranceSpeechItem {
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +0100939 protected final Bundle mParams;
Niels Egbertsdf7deef2014-06-25 15:30:44 +0100940 protected final String mUtteranceId;
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +0100941
Niels Egberts01dedf52016-12-23 15:32:11 +0000942 UtteranceSpeechItemWithParams(
943 Object callerIdentity,
944 int callerUid,
945 int callerPid,
946 Bundle params,
947 String utteranceId) {
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +0100948 super(callerIdentity, callerUid, callerPid);
949 mParams = params;
Niels Egbertsdf7deef2014-06-25 15:30:44 +0100950 mUtteranceId = utteranceId;
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +0100951 }
952
953 boolean hasLanguage() {
954 return !TextUtils.isEmpty(getStringParam(mParams, Engine.KEY_PARAM_LANGUAGE, null));
955 }
956
957 int getSpeechRate() {
958 return getIntParam(mParams, Engine.KEY_PARAM_RATE, getDefaultSpeechRate());
959 }
960
961 int getPitch() {
Sergio Sanchob0cde2c2017-01-26 20:39:05 +0000962 return getIntParam(mParams, Engine.KEY_PARAM_PITCH, getDefaultPitch());
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +0100963 }
964
965 @Override
966 public String getUtteranceId() {
Niels Egbertsdf7deef2014-06-25 15:30:44 +0100967 return mUtteranceId;
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +0100968 }
969
Przemyslaw Szczepaniak5cbf17c2014-06-18 11:35:52 +0100970 AudioOutputParams getAudioParams() {
Niels Egberts01dedf52016-12-23 15:32:11 +0000971 return AudioOutputParams.createFromParamsBundle(mParams, true);
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +0100972 }
973 }
974
Niels Egberts01dedf52016-12-23 15:32:11 +0000975 class SynthesisSpeechItem extends UtteranceSpeechItemWithParams {
Narayan Kamath40f71f02011-11-23 16:42:53 +0000976 // Never null.
Niels Egbertsdf7deef2014-06-25 15:30:44 +0100977 private final CharSequence mText;
Narayan Kamathe22b69a2011-06-08 11:41:47 +0100978 private final SynthesisRequest mSynthesisRequest;
Narayan Kamathe5b8c4d2011-08-22 15:37:47 +0100979 private final String[] mDefaultLocale;
Narayan Kamathe22b69a2011-06-08 11:41:47 +0100980 // Non null after synthesis has started, and all accesses
981 // guarded by 'this'.
982 private AbstractSynthesisCallback mSynthesisCallback;
Niels Egberts01dedf52016-12-23 15:32:11 +0000983 private final EventLogger mEventLogger;
Przemyslaw Szczepaniak65327832013-05-31 13:12:52 +0100984 private final int mCallerUid;
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000985
Niels Egberts01dedf52016-12-23 15:32:11 +0000986 public SynthesisSpeechItem(
987 Object callerIdentity,
988 int callerUid,
989 int callerPid,
990 Bundle params,
991 String utteranceId,
992 CharSequence text) {
Niels Egbertsdf7deef2014-06-25 15:30:44 +0100993 super(callerIdentity, callerUid, callerPid, params, utteranceId);
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000994 mText = text;
Przemyslaw Szczepaniak65327832013-05-31 13:12:52 +0100995 mCallerUid = callerUid;
Narayan Kamathe22b69a2011-06-08 11:41:47 +0100996 mSynthesisRequest = new SynthesisRequest(mText, mParams);
Narayan Kamathe5b8c4d2011-08-22 15:37:47 +0100997 mDefaultLocale = getSettingsLocale();
Narayan Kamathe22b69a2011-06-08 11:41:47 +0100998 setRequestParams(mSynthesisRequest);
Niels Egberts01dedf52016-12-23 15:32:11 +0000999 mEventLogger = new EventLogger(mSynthesisRequest, callerUid, callerPid, mPackageName);
Bjorn Bringert50e657b2011-03-08 16:00:40 +00001000 }
1001
Niels Egbertsdf7deef2014-06-25 15:30:44 +01001002 public CharSequence getText() {
Bjorn Bringert50e657b2011-03-08 16:00:40 +00001003 return mText;
1004 }
1005
1006 @Override
1007 public boolean isValid() {
Narayan Kamath40f71f02011-11-23 16:42:53 +00001008 if (mText == null) {
Narayan Kamath9c3d7a82012-07-20 18:01:43 +01001009 Log.e(TAG, "null synthesis text");
Bjorn Bringert50e657b2011-03-08 16:00:40 +00001010 return false;
1011 }
Przemyslaw Szczepaniak2d940bc2012-11-19 12:22:59 +00001012 if (mText.length() >= TextToSpeech.getMaxSpeechInputLength()) {
Bjorn Bringert50e657b2011-03-08 16:00:40 +00001013 Log.w(TAG, "Text too long: " + mText.length() + " chars");
1014 return false;
1015 }
1016 return true;
1017 }
1018
1019 @Override
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +01001020 protected void playImpl() {
Narayan Kamathe22b69a2011-06-08 11:41:47 +01001021 AbstractSynthesisCallback synthesisCallback;
Narayan Kamath6dabb632011-07-08 12:13:03 +01001022 mEventLogger.onRequestProcessingStart();
Bjorn Bringert50e657b2011-03-08 16:00:40 +00001023 synchronized (this) {
Narayan Kamatha65c62a2011-09-06 17:04:48 +01001024 // stop() might have been called before we enter this
1025 // synchronized block.
1026 if (isStopped()) {
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +01001027 return;
Narayan Kamatha65c62a2011-09-06 17:04:48 +01001028 }
Narayan Kamathe22b69a2011-06-08 11:41:47 +01001029 mSynthesisCallback = createSynthesisCallback();
1030 synthesisCallback = mSynthesisCallback;
Bjorn Bringert50e657b2011-03-08 16:00:40 +00001031 }
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +01001032
Narayan Kamathe22b69a2011-06-08 11:41:47 +01001033 TextToSpeechService.this.onSynthesizeText(mSynthesisRequest, synthesisCallback);
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +01001034
1035 // Fix for case where client called .start() & .error(), but did not called .done()
1036 if (synthesisCallback.hasStarted() && !synthesisCallback.hasFinished()) {
1037 synthesisCallback.done();
1038 }
Bjorn Bringert50e657b2011-03-08 16:00:40 +00001039 }
1040
Narayan Kamathe22b69a2011-06-08 11:41:47 +01001041 protected AbstractSynthesisCallback createSynthesisCallback() {
Przemyslaw Szczepaniak5cbf17c2014-06-18 11:35:52 +01001042 return new PlaybackSynthesisCallback(getAudioParams(),
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +01001043 mAudioPlaybackHandler, this, getCallerIdentity(), mEventLogger, false);
Bjorn Bringert50e657b2011-03-08 16:00:40 +00001044 }
1045
1046 private void setRequestParams(SynthesisRequest request) {
Przemyslaw Szczepaniakad6df742014-07-01 17:04:25 +01001047 String voiceName = getVoiceName();
Narayan Kamathc3edf2a2011-06-15 12:35:06 +01001048 request.setLanguage(getLanguage(), getCountry(), getVariant());
Przemyslaw Szczepaniakad6df742014-07-01 17:04:25 +01001049 if (!TextUtils.isEmpty(voiceName)) {
1050 request.setVoiceName(getVoiceName());
1051 }
Narayan Kamathc3edf2a2011-06-15 12:35:06 +01001052 request.setSpeechRate(getSpeechRate());
Przemyslaw Szczepaniak65327832013-05-31 13:12:52 +01001053 request.setCallerUid(mCallerUid);
Bjorn Bringert50e657b2011-03-08 16:00:40 +00001054 request.setPitch(getPitch());
1055 }
1056
1057 @Override
1058 protected void stopImpl() {
Narayan Kamathe22b69a2011-06-08 11:41:47 +01001059 AbstractSynthesisCallback synthesisCallback;
Bjorn Bringert50e657b2011-03-08 16:00:40 +00001060 synchronized (this) {
Narayan Kamathe22b69a2011-06-08 11:41:47 +01001061 synthesisCallback = mSynthesisCallback;
Bjorn Bringert50e657b2011-03-08 16:00:40 +00001062 }
Narayan Kamatha65c62a2011-09-06 17:04:48 +01001063 if (synthesisCallback != null) {
1064 // If the synthesis callback is null, it implies that we haven't
1065 // entered the synchronized(this) block in playImpl which in
1066 // turn implies that synthesis would not have started.
1067 synthesisCallback.stop();
1068 TextToSpeechService.this.onStop();
Przemyslaw Szczepaniak4b738672014-11-19 17:36:57 +00001069 } else {
1070 dispatchOnStop();
Narayan Kamatha65c62a2011-09-06 17:04:48 +01001071 }
Bjorn Bringert50e657b2011-03-08 16:00:40 +00001072 }
1073
Bjorn Bringert50e657b2011-03-08 16:00:40 +00001074 private String getCountry() {
Narayan Kamathe5b8c4d2011-08-22 15:37:47 +01001075 if (!hasLanguage()) return mDefaultLocale[1];
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +01001076 return getStringParam(mParams, Engine.KEY_PARAM_COUNTRY, "");
Bjorn Bringert50e657b2011-03-08 16:00:40 +00001077 }
1078
1079 private String getVariant() {
Narayan Kamathe5b8c4d2011-08-22 15:37:47 +01001080 if (!hasLanguage()) return mDefaultLocale[2];
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +01001081 return getStringParam(mParams, Engine.KEY_PARAM_VARIANT, "");
Bjorn Bringert50e657b2011-03-08 16:00:40 +00001082 }
1083
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +01001084 public String getLanguage() {
1085 return getStringParam(mParams, Engine.KEY_PARAM_LANGUAGE, mDefaultLocale[0]);
Bjorn Bringert50e657b2011-03-08 16:00:40 +00001086 }
Przemyslaw Szczepaniakad6df742014-07-01 17:04:25 +01001087
1088 public String getVoiceName() {
1089 return getStringParam(mParams, Engine.KEY_PARAM_VOICE_NAME, "");
1090 }
Bjorn Bringert50e657b2011-03-08 16:00:40 +00001091 }
1092
Niels Egberts01dedf52016-12-23 15:32:11 +00001093 private class SynthesisToFileOutputStreamSpeechItem extends SynthesisSpeechItem {
Przemyslaw Szczepaniakfcf671b2013-03-05 11:28:50 +00001094 private final FileOutputStream mFileOutputStream;
Bjorn Bringert50e657b2011-03-08 16:00:40 +00001095
Niels Egberts01dedf52016-12-23 15:32:11 +00001096 public SynthesisToFileOutputStreamSpeechItem(
1097 Object callerIdentity,
1098 int callerUid,
1099 int callerPid,
1100 Bundle params,
1101 String utteranceId,
1102 CharSequence text,
Niels Egbertsdf7deef2014-06-25 15:30:44 +01001103 FileOutputStream fileOutputStream) {
1104 super(callerIdentity, callerUid, callerPid, params, utteranceId, text);
Przemyslaw Szczepaniakfcf671b2013-03-05 11:28:50 +00001105 mFileOutputStream = fileOutputStream;
Bjorn Bringert50e657b2011-03-08 16:00:40 +00001106 }
1107
1108 @Override
Narayan Kamathe22b69a2011-06-08 11:41:47 +01001109 protected AbstractSynthesisCallback createSynthesisCallback() {
Niels Egberts453c13f2015-11-20 12:05:55 +00001110 return new FileSynthesisCallback(mFileOutputStream.getChannel(), this, false);
Bjorn Bringert50e657b2011-03-08 16:00:40 +00001111 }
1112
Narayan Kamath8d1fc242011-06-03 18:11:54 +01001113 @Override
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +01001114 protected void playImpl() {
Narayan Kamath754c72e2011-11-09 14:22:32 +00001115 dispatchOnStart();
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +01001116 super.playImpl();
Przemyslaw Szczepaniakfcf671b2013-03-05 11:28:50 +00001117 try {
1118 mFileOutputStream.close();
1119 } catch(IOException e) {
1120 Log.w(TAG, "Failed to close output file", e);
1121 }
Narayan Kamath8d1fc242011-06-03 18:11:54 +01001122 }
Bjorn Bringert50e657b2011-03-08 16:00:40 +00001123 }
1124
Niels Egberts01dedf52016-12-23 15:32:11 +00001125 private class AudioSpeechItem extends UtteranceSpeechItemWithParams {
Narayan Kamathaf802c62011-12-05 11:20:07 +00001126 private final AudioPlaybackQueueItem mItem;
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +01001127
Niels Egberts01dedf52016-12-23 15:32:11 +00001128 public AudioSpeechItem(
1129 Object callerIdentity,
1130 int callerUid,
1131 int callerPid,
1132 Bundle params,
1133 String utteranceId,
1134 Uri uri) {
Niels Egbertsdf7deef2014-06-25 15:30:44 +01001135 super(callerIdentity, callerUid, callerPid, params, utteranceId);
Narayan Kamathaf802c62011-12-05 11:20:07 +00001136 mItem = new AudioPlaybackQueueItem(this, getCallerIdentity(),
Przemyslaw Szczepaniak5cbf17c2014-06-18 11:35:52 +01001137 TextToSpeechService.this, uri, getAudioParams());
Bjorn Bringert50e657b2011-03-08 16:00:40 +00001138 }
1139
1140 @Override
1141 public boolean isValid() {
1142 return true;
1143 }
1144
1145 @Override
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +01001146 protected void playImpl() {
Narayan Kamathaf802c62011-12-05 11:20:07 +00001147 mAudioPlaybackHandler.enqueue(mItem);
Bjorn Bringert50e657b2011-03-08 16:00:40 +00001148 }
1149
1150 @Override
1151 protected void stopImpl() {
Narayan Kamathbe4ad4a2011-07-15 13:01:09 +01001152 // Do nothing.
Bjorn Bringert50e657b2011-03-08 16:00:40 +00001153 }
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +01001154
1155 @Override
1156 public String getUtteranceId() {
1157 return getStringParam(mParams, Engine.KEY_PARAM_UTTERANCE_ID, null);
1158 }
Przemyslaw Szczepaniak672695e2014-07-14 17:10:42 +01001159
1160 @Override
1161 AudioOutputParams getAudioParams() {
Niels Egberts01dedf52016-12-23 15:32:11 +00001162 return AudioOutputParams.createFromParamsBundle(mParams, false);
Przemyslaw Szczepaniak672695e2014-07-14 17:10:42 +01001163 }
Bjorn Bringert50e657b2011-03-08 16:00:40 +00001164 }
1165
Przemyslaw Szczepaniak13896b72012-11-09 15:18:16 +00001166 private class SilenceSpeechItem extends UtteranceSpeechItem {
Bjorn Bringert50e657b2011-03-08 16:00:40 +00001167 private final long mDuration;
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +01001168 private final String mUtteranceId;
Bjorn Bringert50e657b2011-03-08 16:00:40 +00001169
Narayan Kamath492b7f02011-11-29 17:02:06 +00001170 public SilenceSpeechItem(Object callerIdentity, int callerUid, int callerPid,
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +01001171 String utteranceId, long duration) {
1172 super(callerIdentity, callerUid, callerPid);
1173 mUtteranceId = utteranceId;
Bjorn Bringert50e657b2011-03-08 16:00:40 +00001174 mDuration = duration;
Bjorn Bringert50e657b2011-03-08 16:00:40 +00001175 }
1176
1177 @Override
1178 public boolean isValid() {
1179 return true;
1180 }
1181
1182 @Override
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +01001183 protected void playImpl() {
Narayan Kamath67ae6bc2011-11-30 14:51:00 +00001184 mAudioPlaybackHandler.enqueue(new SilencePlaybackQueueItem(
1185 this, getCallerIdentity(), mDuration));
Bjorn Bringert50e657b2011-03-08 16:00:40 +00001186 }
1187
1188 @Override
1189 protected void stopImpl() {
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +01001190
1191 }
1192
1193 @Override
1194 public String getUtteranceId() {
1195 return mUtteranceId;
Bjorn Bringert50e657b2011-03-08 16:00:40 +00001196 }
1197 }
1198
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +01001199 /**
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +01001200 * Call {@link TextToSpeechService#onLoadLanguage} on synth thread.
1201 */
Przemyslaw Szczepaniak13896b72012-11-09 15:18:16 +00001202 private class LoadLanguageItem extends SpeechItem {
1203 private final String mLanguage;
1204 private final String mCountry;
1205 private final String mVariant;
1206
1207 public LoadLanguageItem(Object callerIdentity, int callerUid, int callerPid,
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +01001208 String language, String country, String variant) {
1209 super(callerIdentity, callerUid, callerPid);
Przemyslaw Szczepaniak13896b72012-11-09 15:18:16 +00001210 mLanguage = language;
1211 mCountry = country;
1212 mVariant = variant;
1213 }
1214
1215 @Override
1216 public boolean isValid() {
1217 return true;
1218 }
1219
1220 @Override
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +01001221 protected void playImpl() {
1222 TextToSpeechService.this.onLoadLanguage(mLanguage, mCountry, mVariant);
Przemyslaw Szczepaniak13896b72012-11-09 15:18:16 +00001223 }
1224
1225 @Override
1226 protected void stopImpl() {
1227 // No-op
1228 }
1229 }
1230
Przemyslaw Szczepaniakad6df742014-07-01 17:04:25 +01001231 /**
1232 * Call {@link TextToSpeechService#onLoadLanguage} on synth thread.
1233 */
1234 private class LoadVoiceItem extends SpeechItem {
1235 private final String mVoiceName;
1236
1237 public LoadVoiceItem(Object callerIdentity, int callerUid, int callerPid,
1238 String voiceName) {
1239 super(callerIdentity, callerUid, callerPid);
1240 mVoiceName = voiceName;
1241 }
1242
1243 @Override
1244 public boolean isValid() {
1245 return true;
1246 }
1247
1248 @Override
1249 protected void playImpl() {
1250 TextToSpeechService.this.onLoadVoice(mVoiceName);
1251 }
1252
1253 @Override
1254 protected void stopImpl() {
1255 // No-op
1256 }
1257 }
1258
1259
Bjorn Bringert50e657b2011-03-08 16:00:40 +00001260 @Override
1261 public IBinder onBind(Intent intent) {
1262 if (TextToSpeech.Engine.INTENT_ACTION_TTS_SERVICE.equals(intent.getAction())) {
1263 return mBinder;
1264 }
1265 return null;
1266 }
1267
1268 /**
Niels Egberts01dedf52016-12-23 15:32:11 +00001269 * Binder returned from {@code #onBind(Intent)}. The methods in this class can be called called
1270 * from several different threads.
Bjorn Bringert50e657b2011-03-08 16:00:40 +00001271 */
Narayan Kamathabc63fb2011-06-10 11:36:57 +01001272 // NOTE: All calls that are passed in a calling app are interned so that
1273 // they can be used as message objects (which are tested for equality using ==).
Niels Egberts01dedf52016-12-23 15:32:11 +00001274 private final ITextToSpeechService.Stub mBinder =
1275 new ITextToSpeechService.Stub() {
1276 @Override
1277 public int speak(
1278 IBinder caller,
1279 CharSequence text,
1280 int queueMode,
1281 Bundle params,
1282 String utteranceId) {
1283 if (!checkNonNull(caller, text, params)) {
1284 return TextToSpeech.ERROR;
1285 }
Narayan Kamathabc63fb2011-06-10 11:36:57 +01001286
Niels Egberts01dedf52016-12-23 15:32:11 +00001287 SpeechItem item =
1288 new SynthesisSpeechItem(
1289 caller,
1290 Binder.getCallingUid(),
1291 Binder.getCallingPid(),
1292 params,
1293 utteranceId,
1294 text);
1295 return mSynthHandler.enqueueSpeechItem(queueMode, item);
Przemyslaw Szczepaniak13896b72012-11-09 15:18:16 +00001296 }
Bjorn Bringert50e657b2011-03-08 16:00:40 +00001297
Niels Egberts01dedf52016-12-23 15:32:11 +00001298 @Override
1299 public int synthesizeToFileDescriptor(
1300 IBinder caller,
1301 CharSequence text,
1302 ParcelFileDescriptor fileDescriptor,
1303 Bundle params,
1304 String utteranceId) {
1305 if (!checkNonNull(caller, text, fileDescriptor, params)) {
1306 return TextToSpeech.ERROR;
1307 }
Przemyslaw Szczepaniakad6df742014-07-01 17:04:25 +01001308
Niels Egberts01dedf52016-12-23 15:32:11 +00001309 // In test env, ParcelFileDescriptor instance may be EXACTLY the same
1310 // one that is used by client. And it will be closed by a client, thus
1311 // preventing us from writing anything to it.
1312 final ParcelFileDescriptor sameFileDescriptor =
1313 ParcelFileDescriptor.adoptFd(fileDescriptor.detachFd());
Przemyslaw Szczepaniakad6df742014-07-01 17:04:25 +01001314
Niels Egberts01dedf52016-12-23 15:32:11 +00001315 SpeechItem item =
1316 new SynthesisToFileOutputStreamSpeechItem(
1317 caller,
1318 Binder.getCallingUid(),
1319 Binder.getCallingPid(),
1320 params,
1321 utteranceId,
1322 text,
1323 new ParcelFileDescriptor.AutoCloseOutputStream(
1324 sameFileDescriptor));
1325 return mSynthHandler.enqueueSpeechItem(TextToSpeech.QUEUE_ADD, item);
Przemyslaw Szczepaniakad6df742014-07-01 17:04:25 +01001326 }
Przemyslaw Szczepaniakad6df742014-07-01 17:04:25 +01001327
Niels Egberts01dedf52016-12-23 15:32:11 +00001328 @Override
1329 public int playAudio(
1330 IBinder caller,
1331 Uri audioUri,
1332 int queueMode,
1333 Bundle params,
1334 String utteranceId) {
1335 if (!checkNonNull(caller, audioUri, params)) {
1336 return TextToSpeech.ERROR;
1337 }
Przemyslaw Szczepaniakad6df742014-07-01 17:04:25 +01001338
Niels Egberts01dedf52016-12-23 15:32:11 +00001339 SpeechItem item =
1340 new AudioSpeechItem(
1341 caller,
1342 Binder.getCallingUid(),
1343 Binder.getCallingPid(),
1344 params,
1345 utteranceId,
1346 audioUri);
1347 return mSynthHandler.enqueueSpeechItem(queueMode, item);
1348 }
Przemyslaw Szczepaniakad6df742014-07-01 17:04:25 +01001349
Niels Egberts01dedf52016-12-23 15:32:11 +00001350 @Override
1351 public int playSilence(
1352 IBinder caller, long duration, int queueMode, String utteranceId) {
1353 if (!checkNonNull(caller)) {
1354 return TextToSpeech.ERROR;
1355 }
Narayan Kamathabc63fb2011-06-10 11:36:57 +01001356
Niels Egberts01dedf52016-12-23 15:32:11 +00001357 SpeechItem item =
1358 new SilenceSpeechItem(
1359 caller,
1360 Binder.getCallingUid(),
1361 Binder.getCallingPid(),
1362 utteranceId,
1363 duration);
1364 return mSynthHandler.enqueueSpeechItem(queueMode, item);
1365 }
Narayan Kamath7a3af862011-06-02 17:28:57 +01001366
Niels Egberts01dedf52016-12-23 15:32:11 +00001367 @Override
1368 public boolean isSpeaking() {
1369 return mSynthHandler.isSpeaking() || mAudioPlaybackHandler.isSpeaking();
1370 }
Narayan Kamathabc63fb2011-06-10 11:36:57 +01001371
Niels Egberts01dedf52016-12-23 15:32:11 +00001372 @Override
1373 public int stop(IBinder caller) {
1374 if (!checkNonNull(caller)) {
1375 return TextToSpeech.ERROR;
1376 }
1377
1378 return mSynthHandler.stopForApp(caller);
1379 }
1380
1381 @Override
1382 public String[] getLanguage() {
1383 return onGetLanguage();
1384 }
1385
1386 @Override
1387 public String[] getClientDefaultLanguage() {
1388 return getSettingsLocale();
1389 }
1390
1391 /*
1392 * If defaults are enforced, then no language is "available" except
1393 * perhaps the default language selected by the user.
1394 */
1395 @Override
1396 public int isLanguageAvailable(String lang, String country, String variant) {
1397 if (!checkNonNull(lang)) {
1398 return TextToSpeech.ERROR;
1399 }
1400
1401 return onIsLanguageAvailable(lang, country, variant);
1402 }
1403
1404 @Override
1405 public String[] getFeaturesForLanguage(
1406 String lang, String country, String variant) {
1407 Set<String> features = onGetFeaturesForLanguage(lang, country, variant);
1408 String[] featuresArray = null;
1409 if (features != null) {
1410 featuresArray = new String[features.size()];
1411 features.toArray(featuresArray);
1412 } else {
1413 featuresArray = new String[0];
1414 }
1415 return featuresArray;
1416 }
1417
1418 /*
1419 * There is no point loading a non default language if defaults
1420 * are enforced.
1421 */
1422 @Override
1423 public int loadLanguage(
1424 IBinder caller, String lang, String country, String variant) {
1425 if (!checkNonNull(lang)) {
1426 return TextToSpeech.ERROR;
1427 }
1428 int retVal = onIsLanguageAvailable(lang, country, variant);
1429
1430 if (retVal == TextToSpeech.LANG_AVAILABLE
1431 || retVal == TextToSpeech.LANG_COUNTRY_AVAILABLE
1432 || retVal == TextToSpeech.LANG_COUNTRY_VAR_AVAILABLE) {
1433
1434 SpeechItem item =
1435 new LoadLanguageItem(
1436 caller,
1437 Binder.getCallingUid(),
1438 Binder.getCallingPid(),
1439 lang,
1440 country,
1441 variant);
1442
1443 if (mSynthHandler.enqueueSpeechItem(TextToSpeech.QUEUE_ADD, item)
1444 != TextToSpeech.SUCCESS) {
1445 return TextToSpeech.ERROR;
1446 }
1447 }
1448 return retVal;
1449 }
1450
1451 @Override
1452 public List<Voice> getVoices() {
1453 return onGetVoices();
1454 }
1455
1456 @Override
1457 public int loadVoice(IBinder caller, String voiceName) {
1458 if (!checkNonNull(voiceName)) {
1459 return TextToSpeech.ERROR;
1460 }
1461 int retVal = onIsValidVoiceName(voiceName);
1462
1463 if (retVal == TextToSpeech.SUCCESS) {
1464 SpeechItem item =
1465 new LoadVoiceItem(
1466 caller,
1467 Binder.getCallingUid(),
1468 Binder.getCallingPid(),
1469 voiceName);
1470 if (mSynthHandler.enqueueSpeechItem(TextToSpeech.QUEUE_ADD, item)
1471 != TextToSpeech.SUCCESS) {
1472 return TextToSpeech.ERROR;
1473 }
1474 }
1475 return retVal;
1476 }
1477
1478 public String getDefaultVoiceNameFor(String lang, String country, String variant) {
1479 if (!checkNonNull(lang)) {
1480 return null;
1481 }
1482 int retVal = onIsLanguageAvailable(lang, country, variant);
1483
1484 if (retVal == TextToSpeech.LANG_AVAILABLE
1485 || retVal == TextToSpeech.LANG_COUNTRY_AVAILABLE
1486 || retVal == TextToSpeech.LANG_COUNTRY_VAR_AVAILABLE) {
1487 return onGetDefaultVoiceNameFor(lang, country, variant);
1488 } else {
1489 return null;
1490 }
1491 }
1492
1493 @Override
1494 public void setCallback(IBinder caller, ITextToSpeechCallback cb) {
1495 // Note that passing in a null callback is a valid use case.
1496 if (!checkNonNull(caller)) {
1497 return;
1498 }
1499
1500 mCallbacks.setCallback(caller, cb);
1501 }
1502
1503 private String intern(String in) {
1504 // The input parameter will be non null.
1505 return in.intern();
1506 }
1507
1508 private boolean checkNonNull(Object... args) {
1509 for (Object o : args) {
1510 if (o == null) return false;
1511 }
1512 return true;
1513 }
1514 };
Bjorn Bringert50e657b2011-03-08 16:00:40 +00001515
1516 private class CallbackMap extends RemoteCallbackList<ITextToSpeechCallback> {
Narayan Kamath492b7f02011-11-29 17:02:06 +00001517 private final HashMap<IBinder, ITextToSpeechCallback> mCallerToCallback
1518 = new HashMap<IBinder, ITextToSpeechCallback>();
Bjorn Bringert50e657b2011-03-08 16:00:40 +00001519
Narayan Kamath492b7f02011-11-29 17:02:06 +00001520 public void setCallback(IBinder caller, ITextToSpeechCallback cb) {
1521 synchronized (mCallerToCallback) {
Bjorn Bringert50e657b2011-03-08 16:00:40 +00001522 ITextToSpeechCallback old;
1523 if (cb != null) {
Narayan Kamath492b7f02011-11-29 17:02:06 +00001524 register(cb, caller);
1525 old = mCallerToCallback.put(caller, cb);
Bjorn Bringert50e657b2011-03-08 16:00:40 +00001526 } else {
Narayan Kamath492b7f02011-11-29 17:02:06 +00001527 old = mCallerToCallback.remove(caller);
Bjorn Bringert50e657b2011-03-08 16:00:40 +00001528 }
1529 if (old != null && old != cb) {
1530 unregister(old);
1531 }
1532 }
1533 }
1534
Przemyslaw Szczepaniak4b738672014-11-19 17:36:57 +00001535 public void dispatchOnStop(Object callerIdentity, String utteranceId, boolean started) {
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +01001536 ITextToSpeechCallback cb = getCallbackFor(callerIdentity);
1537 if (cb == null) return;
1538 try {
Przemyslaw Szczepaniak4b738672014-11-19 17:36:57 +00001539 cb.onStop(utteranceId, started);
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +01001540 } catch (RemoteException e) {
1541 Log.e(TAG, "Callback onStop failed: " + e);
1542 }
1543 }
1544
1545 public void dispatchOnSuccess(Object callerIdentity, String utteranceId) {
1546 ITextToSpeechCallback cb = getCallbackFor(callerIdentity);
1547 if (cb == null) return;
1548 try {
1549 cb.onSuccess(utteranceId);
Bjorn Bringert50e657b2011-03-08 16:00:40 +00001550 } catch (RemoteException e) {
Narayan Kamath754c72e2011-11-09 14:22:32 +00001551 Log.e(TAG, "Callback onDone failed: " + e);
1552 }
1553 }
1554
Narayan Kamath492b7f02011-11-29 17:02:06 +00001555 public void dispatchOnStart(Object callerIdentity, String utteranceId) {
1556 ITextToSpeechCallback cb = getCallbackFor(callerIdentity);
Narayan Kamath754c72e2011-11-09 14:22:32 +00001557 if (cb == null) return;
1558 try {
1559 cb.onStart(utteranceId);
1560 } catch (RemoteException e) {
1561 Log.e(TAG, "Callback onStart failed: " + e);
1562 }
Narayan Kamath754c72e2011-11-09 14:22:32 +00001563 }
1564
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +01001565 public void dispatchOnError(Object callerIdentity, String utteranceId,
1566 int errorCode) {
Narayan Kamath492b7f02011-11-29 17:02:06 +00001567 ITextToSpeechCallback cb = getCallbackFor(callerIdentity);
Narayan Kamath754c72e2011-11-09 14:22:32 +00001568 if (cb == null) return;
1569 try {
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +01001570 cb.onError(utteranceId, errorCode);
Narayan Kamath754c72e2011-11-09 14:22:32 +00001571 } catch (RemoteException e) {
1572 Log.e(TAG, "Callback onError failed: " + e);
Bjorn Bringert50e657b2011-03-08 16:00:40 +00001573 }
1574 }
1575
Niels Egbertsc99ba1c2015-11-20 11:44:25 +00001576 public void dispatchOnBeginSynthesis(Object callerIdentity, String utteranceId, int sampleRateInHz, int audioFormat, int channelCount) {
1577 ITextToSpeechCallback cb = getCallbackFor(callerIdentity);
1578 if (cb == null) return;
1579 try {
1580 cb.onBeginSynthesis(utteranceId, sampleRateInHz, audioFormat, channelCount);
1581 } catch (RemoteException e) {
1582 Log.e(TAG, "Callback dispatchOnBeginSynthesis(String, int, int, int) failed: " + e);
1583 }
1584 }
1585
1586 public void dispatchOnAudioAvailable(Object callerIdentity, String utteranceId, byte[] buffer) {
1587 ITextToSpeechCallback cb = getCallbackFor(callerIdentity);
1588 if (cb == null) return;
1589 try {
1590 cb.onAudioAvailable(utteranceId, buffer);
1591 } catch (RemoteException e) {
1592 Log.e(TAG, "Callback dispatchOnAudioAvailable(String, byte[]) failed: " + e);
1593 }
1594 }
1595
Niels Egberts5d0ea0f2017-03-21 15:29:24 +00001596 public void dispatchOnRangeStart(
1597 Object callerIdentity, String utteranceId, int start, int end, int frame) {
Niels Egberts65c50782016-12-23 12:01:32 +00001598 ITextToSpeechCallback cb = getCallbackFor(callerIdentity);
1599 if (cb == null) return;
1600 try {
Niels Egberts5d0ea0f2017-03-21 15:29:24 +00001601 cb.onRangeStart(utteranceId, start, end, frame);
Niels Egberts65c50782016-12-23 12:01:32 +00001602 } catch (RemoteException e) {
Niels Egberts5d0ea0f2017-03-21 15:29:24 +00001603 Log.e(TAG, "Callback dispatchOnRangeStart(String, int, int, int) failed: " + e);
Niels Egberts65c50782016-12-23 12:01:32 +00001604 }
1605 }
1606
Bjorn Bringert50e657b2011-03-08 16:00:40 +00001607 @Override
1608 public void onCallbackDied(ITextToSpeechCallback callback, Object cookie) {
Narayan Kamath492b7f02011-11-29 17:02:06 +00001609 IBinder caller = (IBinder) cookie;
1610 synchronized (mCallerToCallback) {
1611 mCallerToCallback.remove(caller);
Bjorn Bringert50e657b2011-03-08 16:00:40 +00001612 }
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +01001613 //mSynthHandler.stopForApp(caller);
Bjorn Bringert50e657b2011-03-08 16:00:40 +00001614 }
1615
1616 @Override
1617 public void kill() {
Narayan Kamath492b7f02011-11-29 17:02:06 +00001618 synchronized (mCallerToCallback) {
1619 mCallerToCallback.clear();
Bjorn Bringert50e657b2011-03-08 16:00:40 +00001620 super.kill();
1621 }
1622 }
1623
Narayan Kamath492b7f02011-11-29 17:02:06 +00001624 private ITextToSpeechCallback getCallbackFor(Object caller) {
Narayan Kamath754c72e2011-11-09 14:22:32 +00001625 ITextToSpeechCallback cb;
Narayan Kamath492b7f02011-11-29 17:02:06 +00001626 IBinder asBinder = (IBinder) caller;
1627 synchronized (mCallerToCallback) {
1628 cb = mCallerToCallback.get(asBinder);
Narayan Kamath754c72e2011-11-09 14:22:32 +00001629 }
1630
1631 return cb;
1632 }
Bjorn Bringert50e657b2011-03-08 16:00:40 +00001633 }
Bjorn Bringert50e657b2011-03-08 16:00:40 +00001634}