blob: d7c51fcb29a279fd4ae3058b17f6018dd4360f21 [file] [log] [blame]
Bjorn Bringert50e657b2011-03-08 16:00:40 +00001/*
2 * Copyright (C) 2011 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5 * use this file except in compliance with the License. You may obtain a copy of
6 * the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13 * License for the specific language governing permissions and limitations under
14 * the License.
15 */
16package android.speech.tts;
17
18import android.app.Service;
19import android.content.Intent;
20import android.net.Uri;
Narayan Kamath492b7f02011-11-29 17:02:06 +000021import android.os.Binder;
Bjorn Bringert50e657b2011-03-08 16:00:40 +000022import android.os.Bundle;
Bjorn Bringert50e657b2011-03-08 16:00:40 +000023import android.os.Handler;
24import android.os.HandlerThread;
25import android.os.IBinder;
26import android.os.Looper;
27import android.os.Message;
28import android.os.MessageQueue;
Przemyslaw Szczepaniak5acb33a2013-02-08 16:36:25 +000029import android.os.ParcelFileDescriptor;
Bjorn Bringert50e657b2011-03-08 16:00:40 +000030import android.os.RemoteCallbackList;
31import android.os.RemoteException;
32import android.provider.Settings;
33import android.speech.tts.TextToSpeech.Engine;
34import android.text.TextUtils;
35import android.util.Log;
36
Przemyslaw Szczepaniak5acb33a2013-02-08 16:36:25 +000037import java.io.FileOutputStream;
Przemyslaw Szczepaniakfcf671b2013-03-05 11:28:50 +000038import java.io.IOException;
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +010039import java.util.ArrayList;
Bjorn Bringert50e657b2011-03-08 16:00:40 +000040import java.util.HashMap;
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +010041import java.util.List;
Bjorn Bringert50e657b2011-03-08 16:00:40 +000042import java.util.Locale;
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +010043import java.util.Map;
44import java.util.MissingResourceException;
Narayan Kamath748af662011-10-31 14:20:01 +000045import java.util.Set;
Bjorn Bringert50e657b2011-03-08 16:00:40 +000046
47
48/**
Narayan Kamathe22b69a2011-06-08 11:41:47 +010049 * Abstract base class for TTS engine implementations. The following methods
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +010050 * need to be implemented for V1 API ({@link TextToSpeech}) implementation.
Narayan Kamathe22b69a2011-06-08 11:41:47 +010051 * <ul>
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +010052 * <li>{@link #onIsLanguageAvailable}</li>
53 * <li>{@link #onLoadLanguage}</li>
54 * <li>{@link #onGetLanguage}</li>
55 * <li>{@link #onSynthesizeText}</li>
56 * <li>{@link #onStop}</li>
Narayan Kamathe22b69a2011-06-08 11:41:47 +010057 * </ul>
Narayan Kamathe22b69a2011-06-08 11:41:47 +010058 * The first three deal primarily with language management, and are used to
59 * query the engine for it's support for a given language and indicate to it
60 * that requests in a given language are imminent.
61 *
62 * {@link #onSynthesizeText} is central to the engine implementation. The
63 * implementation should synthesize text as per the request parameters and
64 * return synthesized data via the supplied callback. This class and its helpers
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +010065 * will then consume that data, which might mean queuing it for playback or writing
66 * it to a file or similar. All calls to this method will be on a single thread,
67 * which will be different from the main thread of the service. Synthesis must be
68 * synchronous which means the engine must NOT hold on to the callback or call any
69 * methods on it after the method returns.
Narayan Kamathe22b69a2011-06-08 11:41:47 +010070 *
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +010071 * {@link #onStop} tells the engine that it should stop
72 * all ongoing synthesis, if any. Any pending data from the current synthesis
73 * will be discarded.
Narayan Kamathe5b8c4d2011-08-22 15:37:47 +010074 *
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +010075 * {@link #onGetLanguage} is not required as of JELLYBEAN_MR2 (API 18) and later, it is only
76 * called on earlier versions of Android.
77 * <p>
78 * In order to fully support the V2 API ({@link TextToSpeechClient}),
79 * these methods must be implemented:
80 * <ul>
81 * <li>{@link #onSynthesizeTextV2}</li>
82 * <li>{@link #checkVoicesInfo}</li>
83 * <li>{@link #onVoicesInfoChange}</li>
84 * <li>{@link #implementsV2API}</li>
85 * </ul>
86 * In addition {@link #implementsV2API} has to return true.
87 * <p>
88 * If the service does not implement these methods and {@link #implementsV2API} returns false,
89 * then the V2 API will be provided by converting V2 requests ({@link #onSynthesizeTextV2})
90 * to V1 requests ({@link #onSynthesizeText}). On service setup, all of the available device
91 * locales will be fed to {@link #onIsLanguageAvailable} to check if they are supported.
92 * If they are, embedded and/or network voices will be created depending on the result of
93 * {@link #onGetFeaturesForLanguage}.
94 * <p>
95 * Note that a V2 service will still receive requests from V1 clients and has to implement all
96 * of the V1 API methods.
Bjorn Bringert50e657b2011-03-08 16:00:40 +000097 */
98public abstract class TextToSpeechService extends Service {
99
100 private static final boolean DBG = false;
101 private static final String TAG = "TextToSpeechService";
102
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000103 private static final String SYNTH_THREAD_NAME = "SynthThread";
104
105 private SynthHandler mSynthHandler;
Narayan Kamath8d1fc242011-06-03 18:11:54 +0100106 // A thread and it's associated handler for playing back any audio
107 // associated with this TTS engine. Will handle all requests except synthesis
108 // to file requests, which occur on the synthesis thread.
109 private AudioPlaybackHandler mAudioPlaybackHandler;
Narayan Kamathe5b8c4d2011-08-22 15:37:47 +0100110 private TtsEngines mEngineHelper;
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000111
112 private CallbackMap mCallbacks;
Narayan Kamath6dabb632011-07-08 12:13:03 +0100113 private String mPackageName;
Narayan Kamath7a3af862011-06-02 17:28:57 +0100114
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +0100115 private final Object mVoicesInfoLock = new Object();
116
117 private List<VoiceInfo> mVoicesInfoList;
Przemyslaw Szczepaniak97cd6472013-10-25 12:04:47 +0100118 private Map<String, VoiceInfo> mVoicesInfoLookup;
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +0100119
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000120 @Override
121 public void onCreate() {
122 if (DBG) Log.d(TAG, "onCreate()");
123 super.onCreate();
124
125 SynthThread synthThread = new SynthThread();
126 synthThread.start();
127 mSynthHandler = new SynthHandler(synthThread.getLooper());
128
Narayan Kamath4924fe32011-06-09 11:35:13 +0100129 mAudioPlaybackHandler = new AudioPlaybackHandler();
130 mAudioPlaybackHandler.start();
Narayan Kamathc90f1c82011-05-24 11:39:43 +0100131
Narayan Kamathe5b8c4d2011-08-22 15:37:47 +0100132 mEngineHelper = new TtsEngines(this);
133
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000134 mCallbacks = new CallbackMap();
135
Narayan Kamath6dabb632011-07-08 12:13:03 +0100136 mPackageName = getApplicationInfo().packageName;
137
Narayan Kamathe5b8c4d2011-08-22 15:37:47 +0100138 String[] defaultLocale = getSettingsLocale();
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +0100139
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000140 // Load default language
Narayan Kamathe5b8c4d2011-08-22 15:37:47 +0100141 onLoadLanguage(defaultLocale[0], defaultLocale[1], defaultLocale[2]);
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000142 }
143
144 @Override
145 public void onDestroy() {
146 if (DBG) Log.d(TAG, "onDestroy()");
147
148 // Tell the synthesizer to stop
149 mSynthHandler.quit();
Narayan Kamath8d1fc242011-06-03 18:11:54 +0100150 // Tell the audio playback thread to stop.
151 mAudioPlaybackHandler.quit();
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000152 // Unregister all callbacks.
153 mCallbacks.kill();
154
155 super.onDestroy();
156 }
157
158 /**
159 * Checks whether the engine supports a given language.
160 *
161 * Can be called on multiple threads.
162 *
Przemyslaw Szczepaniak13896b72012-11-09 15:18:16 +0000163 * Its return values HAVE to be consistent with onLoadLanguage.
164 *
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000165 * @param lang ISO-3 language code.
166 * @param country ISO-3 country code. May be empty or null.
167 * @param variant Language variant. May be empty or null.
168 * @return Code indicating the support status for the locale.
169 * One of {@link TextToSpeech#LANG_AVAILABLE},
170 * {@link TextToSpeech#LANG_COUNTRY_AVAILABLE},
171 * {@link TextToSpeech#LANG_COUNTRY_VAR_AVAILABLE},
172 * {@link TextToSpeech#LANG_MISSING_DATA}
173 * {@link TextToSpeech#LANG_NOT_SUPPORTED}.
174 */
175 protected abstract int onIsLanguageAvailable(String lang, String country, String variant);
176
177 /**
178 * Returns the language, country and variant currently being used by the TTS engine.
179 *
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +0100180 * This method will be called only on Android 4.2 and before (API <= 17). In later versions
181 * this method is not called by the Android TTS framework.
182 *
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000183 * Can be called on multiple threads.
184 *
185 * @return A 3-element array, containing language (ISO 3-letter code),
186 * country (ISO 3-letter code) and variant used by the engine.
187 * The country and variant may be {@code ""}. If country is empty, then variant must
188 * be empty too.
189 * @see Locale#getISO3Language()
190 * @see Locale#getISO3Country()
191 * @see Locale#getVariant()
192 */
193 protected abstract String[] onGetLanguage();
194
195 /**
196 * Notifies the engine that it should load a speech synthesis language. There is no guarantee
197 * that this method is always called before the language is used for synthesis. It is merely
198 * a hint to the engine that it will probably get some synthesis requests for this language
199 * at some point in the future.
200 *
201 * Can be called on multiple threads.
Przemyslaw Szczepaniak13896b72012-11-09 15:18:16 +0000202 * In <= Android 4.2 (<= API 17) can be called on main and service binder threads.
203 * In > Android 4.2 (> API 17) can be called on main and synthesis threads.
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000204 *
205 * @param lang ISO-3 language code.
206 * @param country ISO-3 country code. May be empty or null.
207 * @param variant Language variant. May be empty or null.
208 * @return Code indicating the support status for the locale.
209 * One of {@link TextToSpeech#LANG_AVAILABLE},
210 * {@link TextToSpeech#LANG_COUNTRY_AVAILABLE},
211 * {@link TextToSpeech#LANG_COUNTRY_VAR_AVAILABLE},
212 * {@link TextToSpeech#LANG_MISSING_DATA}
213 * {@link TextToSpeech#LANG_NOT_SUPPORTED}.
214 */
215 protected abstract int onLoadLanguage(String lang, String country, String variant);
216
217 /**
218 * Notifies the service that it should stop any in-progress speech synthesis.
219 * This method can be called even if no speech synthesis is currently in progress.
220 *
221 * Can be called on multiple threads, but not on the synthesis thread.
222 */
223 protected abstract void onStop();
224
225 /**
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +0100226 * Tells the service to synthesize speech from the given text. This method
227 * should block until the synthesis is finished. Used for requests from V1
228 * clients ({@link android.speech.tts.TextToSpeech}). Called on the synthesis
229 * thread.
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000230 *
Narayan Kamathe22b69a2011-06-08 11:41:47 +0100231 * @param request The synthesis request.
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +0100232 * @param callback The callback that the engine must use to make data
233 * available for playback or for writing to a file.
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000234 */
Narayan Kamathe22b69a2011-06-08 11:41:47 +0100235 protected abstract void onSynthesizeText(SynthesisRequest request,
236 SynthesisCallback callback);
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000237
Narayan Kamath748af662011-10-31 14:20:01 +0000238 /**
Przemyslaw Szczepaniak97cd6472013-10-25 12:04:47 +0100239 * Check the available voices data and return an immutable list of the available voices.
240 * The output of this method will be passed to clients to allow them to configure synthesis
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +0100241 * requests.
242 *
243 * Can be called on multiple threads.
244 *
245 * The result of this method will be saved and served to all TTS clients. If a TTS service wants
246 * to update the set of available voices, it should call the {@link #forceVoicesInfoCheck()}
247 * method.
248 */
249 protected List<VoiceInfo> checkVoicesInfo() {
250 if (implementsV2API()) {
251 throw new IllegalStateException("For proper V2 API implementation this method has to" +
252 " be implemented");
253 }
254
255 // V2 to V1 interface adapter. This allows using V2 client interface on V1-only services.
256 Bundle defaultParams = new Bundle();
257 defaultParams.putFloat(TextToSpeechClient.Params.SPEECH_PITCH, 1.0f);
258 defaultParams.putFloat(TextToSpeechClient.Params.SPEECH_SPEED, -1.0f);
259
260 // Enumerate all locales and check if they are available
261 ArrayList<VoiceInfo> voicesInfo = new ArrayList<VoiceInfo>();
262 int id = 0;
263 for (Locale locale : Locale.getAvailableLocales()) {
264 int expectedStatus = TextToSpeech.LANG_COUNTRY_VAR_AVAILABLE;
265 if (locale.getVariant().isEmpty()) {
266 if (locale.getCountry().isEmpty()) {
267 expectedStatus = TextToSpeech.LANG_AVAILABLE;
268 } else {
269 expectedStatus = TextToSpeech.LANG_COUNTRY_AVAILABLE;
270 }
271 }
272 try {
273 int localeStatus = onIsLanguageAvailable(locale.getISO3Language(),
274 locale.getISO3Country(), locale.getVariant());
275 if (localeStatus != expectedStatus) {
276 continue;
277 }
278 } catch (MissingResourceException e) {
279 // Ignore locale without iso 3 codes
280 continue;
281 }
282
283 Set<String> features = onGetFeaturesForLanguage(locale.getISO3Language(),
284 locale.getISO3Country(), locale.getVariant());
285
286 VoiceInfo.Builder builder = new VoiceInfo.Builder();
287 builder.setLatency(VoiceInfo.LATENCY_NORMAL);
288 builder.setQuality(VoiceInfo.QUALITY_NORMAL);
289 builder.setLocale(locale);
290 builder.setParamsWithDefaults(defaultParams);
291
292 if (features == null || features.contains(
293 TextToSpeech.Engine.KEY_FEATURE_EMBEDDED_SYNTHESIS)) {
Przemyslaw Szczepaniak97cd6472013-10-25 12:04:47 +0100294 builder.setName(locale.toString() + "-embedded");
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +0100295 builder.setRequiresNetworkConnection(false);
296 voicesInfo.add(builder.build());
297 }
298
299 if (features != null && features.contains(
300 TextToSpeech.Engine.KEY_FEATURE_NETWORK_SYNTHESIS)) {
Przemyslaw Szczepaniak97cd6472013-10-25 12:04:47 +0100301 builder.setName(locale.toString() + "-network");
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +0100302 builder.setRequiresNetworkConnection(true);
303 voicesInfo.add(builder.build());
304 }
305 }
306
307 return voicesInfo;
308 }
309
310 /**
311 * Tells the synthesis thread that it should reload voice data.
312 * There's a high probability that the underlying set of available voice data has changed.
313 * Called only on the synthesis thread.
314 */
315 protected void onVoicesInfoChange() {
316
317 }
318
319 /**
320 * Tells the service to synthesize speech from the given text. This method
321 * should block until the synthesis is finished. Used for requests from V2
322 * client {@link android.speech.tts.TextToSpeechClient}. Called on the
323 * synthesis thread.
324 *
325 * @param request The synthesis request.
326 * @param callback The callback the the engine must use to make data
327 * available for playback or for writing to a file.
328 */
329 protected void onSynthesizeTextV2(SynthesisRequestV2 request,
330 VoiceInfo selectedVoice,
331 SynthesisCallback callback) {
332 if (implementsV2API()) {
333 throw new IllegalStateException("For proper V2 API implementation this method has to" +
334 " be implemented");
335 }
336
337 // Convert to V1 params
338 int speechRate = (int) (request.getVoiceParams().getFloat(
339 TextToSpeechClient.Params.SPEECH_SPEED, 1.0f) * 100);
340 int speechPitch = (int) (request.getVoiceParams().getFloat(
341 TextToSpeechClient.Params.SPEECH_PITCH, 1.0f) * 100);
342
343 // Provide adapter to V1 API
344 Bundle params = new Bundle();
345 params.putString(TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID, request.getUtteranceId());
346 params.putInt(TextToSpeech.Engine.KEY_PARAM_PITCH, speechPitch);
347 params.putInt(TextToSpeech.Engine.KEY_PARAM_RATE, speechRate);
348 if (selectedVoice.getRequiresNetworkConnection()) {
349 params.putString(TextToSpeech.Engine.KEY_FEATURE_NETWORK_SYNTHESIS, "true");
350 } else {
351 params.putString(TextToSpeech.Engine.KEY_FEATURE_EMBEDDED_SYNTHESIS, "true");
352 }
353
354 // Build V1 request
355 SynthesisRequest requestV1 = new SynthesisRequest(request.getText(), params);
356 Locale locale = selectedVoice.getLocale();
357 requestV1.setLanguage(locale.getISO3Language(), locale.getISO3Country(),
358 locale.getVariant());
359 requestV1.setSpeechRate(speechRate);
360 requestV1.setPitch(speechPitch);
361
362 // Synthesize using V1 interface
363 onSynthesizeText(requestV1, callback);
364 }
365
366 /**
367 * If true, this service implements proper V2 TTS API service. If it's false,
368 * V2 API will be provided through adapter.
369 */
370 protected boolean implementsV2API() {
371 return false;
372 }
373
374 /**
Narayan Kamath748af662011-10-31 14:20:01 +0000375 * Queries the service for a set of features supported for a given language.
376 *
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +0100377 * Can be called on multiple threads.
378 *
Narayan Kamath748af662011-10-31 14:20:01 +0000379 * @param lang ISO-3 language code.
380 * @param country ISO-3 country code. May be empty or null.
381 * @param variant Language variant. May be empty or null.
382 * @return A list of features supported for the given language.
383 */
384 protected Set<String> onGetFeaturesForLanguage(String lang, String country, String variant) {
385 return null;
386 }
387
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +0100388 private List<VoiceInfo> getVoicesInfo() {
389 synchronized (mVoicesInfoLock) {
390 if (mVoicesInfoList == null) {
391 // Get voices. Defensive copy to make sure TTS engine won't alter the list.
392 mVoicesInfoList = new ArrayList<VoiceInfo>(checkVoicesInfo());
393 // Build lookup map
Przemyslaw Szczepaniak97cd6472013-10-25 12:04:47 +0100394 mVoicesInfoLookup = new HashMap<String, VoiceInfo>((int) (
395 mVoicesInfoList.size()*1.5f));
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +0100396 for (VoiceInfo voiceInfo : mVoicesInfoList) {
Przemyslaw Szczepaniak97cd6472013-10-25 12:04:47 +0100397 VoiceInfo prev = mVoicesInfoLookup.put(voiceInfo.getName(), voiceInfo);
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +0100398 if (prev != null) {
Przemyslaw Szczepaniak97cd6472013-10-25 12:04:47 +0100399 Log.e(TAG, "Duplicate name (" + voiceInfo.getName() + ") of the voice ");
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +0100400 }
401 }
402 }
403 return mVoicesInfoList;
404 }
405 }
406
Przemyslaw Szczepaniak97cd6472013-10-25 12:04:47 +0100407 public VoiceInfo getVoicesInfoWithName(String name) {
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +0100408 synchronized (mVoicesInfoLock) {
409 if (mVoicesInfoLookup != null) {
Przemyslaw Szczepaniak97cd6472013-10-25 12:04:47 +0100410 return mVoicesInfoLookup.get(name);
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +0100411 }
412 }
413 return null;
414 }
415
416 /**
417 * Force TTS service to reevaluate the set of available languages. Will result in
418 * a call to {@link #checkVoicesInfo()} on the same thread, {@link #onVoicesInfoChange}
419 * on the synthesizer thread and callback to
420 * {@link TextToSpeechClient.ConnectionCallbacks#onEngineStatusChange} of all connected
421 * TTS clients.
422 *
423 * Use this method only if you know that set of available languages changed.
424 *
425 * Can be called on multiple threads.
426 */
427 public void forceVoicesInfoCheck() {
428 synchronized (mVoicesInfoLock) {
429 List<VoiceInfo> old = mVoicesInfoList;
430
431 mVoicesInfoList = null; // Force recreation of voices info list
432 getVoicesInfo();
433
434 if (mVoicesInfoList == null) {
435 throw new IllegalStateException("This method applies only to services " +
436 "supporting V2 TTS API. This services doesn't support V2 TTS API.");
437 }
438
439 if (old != null) {
440 // Flush all existing items, and inform synthesis thread about the change.
441 mSynthHandler.enqueueSpeechItem(TextToSpeech.QUEUE_FLUSH,
442 new VoicesInfoChangeItem());
443 // TODO: Handle items that may be added to queue after SynthesizerRestartItem
444 // but before client reconnection
445 // Disconnect all of them
446 mCallbacks.dispatchVoicesInfoChange(mVoicesInfoList);
447 }
448 }
449 }
450
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000451 private int getDefaultSpeechRate() {
452 return getSecureSettingInt(Settings.Secure.TTS_DEFAULT_RATE, Engine.DEFAULT_RATE);
453 }
454
Narayan Kamathe5b8c4d2011-08-22 15:37:47 +0100455 private String[] getSettingsLocale() {
456 final String locale = mEngineHelper.getLocalePrefForEngine(mPackageName);
457 return TtsEngines.parseLocalePref(locale);
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000458 }
459
460 private int getSecureSettingInt(String name, int defaultValue) {
461 return Settings.Secure.getInt(getContentResolver(), name, defaultValue);
462 }
463
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000464 /**
465 * Synthesizer thread. This thread is used to run {@link SynthHandler}.
466 */
467 private class SynthThread extends HandlerThread implements MessageQueue.IdleHandler {
468
469 private boolean mFirstIdle = true;
470
471 public SynthThread() {
Narayan Kamath84deb602011-07-27 13:22:09 +0100472 super(SYNTH_THREAD_NAME, android.os.Process.THREAD_PRIORITY_DEFAULT);
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000473 }
474
475 @Override
476 protected void onLooperPrepared() {
477 getLooper().getQueue().addIdleHandler(this);
478 }
479
480 @Override
481 public boolean queueIdle() {
482 if (mFirstIdle) {
483 mFirstIdle = false;
484 } else {
485 broadcastTtsQueueProcessingCompleted();
486 }
487 return true;
488 }
489
490 private void broadcastTtsQueueProcessingCompleted() {
491 Intent i = new Intent(TextToSpeech.ACTION_TTS_QUEUE_PROCESSING_COMPLETED);
492 if (DBG) Log.d(TAG, "Broadcasting: " + i);
493 sendBroadcast(i);
494 }
495 }
496
497 private class SynthHandler extends Handler {
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000498 private SpeechItem mCurrentSpeechItem = null;
499
500 public SynthHandler(Looper looper) {
501 super(looper);
502 }
503
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000504 private synchronized SpeechItem getCurrentSpeechItem() {
505 return mCurrentSpeechItem;
506 }
507
508 private synchronized SpeechItem setCurrentSpeechItem(SpeechItem speechItem) {
509 SpeechItem old = mCurrentSpeechItem;
510 mCurrentSpeechItem = speechItem;
511 return old;
512 }
513
Narayan Kamath492b7f02011-11-29 17:02:06 +0000514 private synchronized SpeechItem maybeRemoveCurrentSpeechItem(Object callerIdentity) {
Narayan Kamatha65c62a2011-09-06 17:04:48 +0100515 if (mCurrentSpeechItem != null &&
Przemyslaw Szczepaniak13896b72012-11-09 15:18:16 +0000516 (mCurrentSpeechItem.getCallerIdentity() == callerIdentity)) {
Narayan Kamatha65c62a2011-09-06 17:04:48 +0100517 SpeechItem current = mCurrentSpeechItem;
518 mCurrentSpeechItem = null;
519 return current;
520 }
521
522 return null;
523 }
524
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000525 public boolean isSpeaking() {
526 return getCurrentSpeechItem() != null;
527 }
528
529 public void quit() {
530 // Don't process any more speech items
531 getLooper().quit();
532 // Stop the current speech item
533 SpeechItem current = setCurrentSpeechItem(null);
534 if (current != null) {
535 current.stop();
536 }
Narayan Kamathbe4ad4a2011-07-15 13:01:09 +0100537 // The AudioPlaybackHandler will be destroyed by the caller.
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000538 }
539
540 /**
541 * Adds a speech item to the queue.
542 *
543 * Called on a service binder thread.
544 */
545 public int enqueueSpeechItem(int queueMode, final SpeechItem speechItem) {
Przemyslaw Szczepaniak13896b72012-11-09 15:18:16 +0000546 UtteranceProgressDispatcher utterenceProgress = null;
547 if (speechItem instanceof UtteranceProgressDispatcher) {
548 utterenceProgress = (UtteranceProgressDispatcher) speechItem;
549 }
550
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000551 if (!speechItem.isValid()) {
Przemyslaw Szczepaniak13896b72012-11-09 15:18:16 +0000552 if (utterenceProgress != null) {
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +0100553 utterenceProgress.dispatchOnError(
554 TextToSpeechClient.Status.ERROR_INVALID_REQUEST);
Przemyslaw Szczepaniak13896b72012-11-09 15:18:16 +0000555 }
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000556 return TextToSpeech.ERROR;
557 }
Narayan Kamath4924fe32011-06-09 11:35:13 +0100558
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000559 if (queueMode == TextToSpeech.QUEUE_FLUSH) {
Narayan Kamath492b7f02011-11-29 17:02:06 +0000560 stopForApp(speechItem.getCallerIdentity());
Narayan Kamathabc63fb2011-06-10 11:36:57 +0100561 } else if (queueMode == TextToSpeech.QUEUE_DESTROY) {
Narayan Kamatha65c62a2011-09-06 17:04:48 +0100562 stopAll();
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000563 }
564 Runnable runnable = new Runnable() {
565 @Override
566 public void run() {
567 setCurrentSpeechItem(speechItem);
Narayan Kamath8d1fc242011-06-03 18:11:54 +0100568 speechItem.play();
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000569 setCurrentSpeechItem(null);
570 }
571 };
572 Message msg = Message.obtain(this, runnable);
Przemyslaw Szczepaniak13896b72012-11-09 15:18:16 +0000573
Narayan Kamatha65c62a2011-09-06 17:04:48 +0100574 // The obj is used to remove all callbacks from the given app in
575 // stopForApp(String).
Narayan Kamathabc63fb2011-06-10 11:36:57 +0100576 //
577 // Note that this string is interned, so the == comparison works.
Narayan Kamath492b7f02011-11-29 17:02:06 +0000578 msg.obj = speechItem.getCallerIdentity();
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +0100579
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000580 if (sendMessage(msg)) {
581 return TextToSpeech.SUCCESS;
582 } else {
583 Log.w(TAG, "SynthThread has quit");
Przemyslaw Szczepaniak13896b72012-11-09 15:18:16 +0000584 if (utterenceProgress != null) {
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +0100585 utterenceProgress.dispatchOnError(TextToSpeechClient.Status.ERROR_SERVICE);
Przemyslaw Szczepaniak13896b72012-11-09 15:18:16 +0000586 }
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000587 return TextToSpeech.ERROR;
588 }
589 }
590
591 /**
592 * Stops all speech output and removes any utterances still in the queue for
593 * the calling app.
594 *
595 * Called on a service binder thread.
596 */
Narayan Kamath492b7f02011-11-29 17:02:06 +0000597 public int stopForApp(Object callerIdentity) {
598 if (callerIdentity == null) {
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000599 return TextToSpeech.ERROR;
600 }
Narayan Kamath4924fe32011-06-09 11:35:13 +0100601
Narayan Kamath492b7f02011-11-29 17:02:06 +0000602 removeCallbacksAndMessages(callerIdentity);
Narayan Kamathbe4ad4a2011-07-15 13:01:09 +0100603 // This stops writing data to the file / or publishing
604 // items to the audio playback handler.
Narayan Kamatha65c62a2011-09-06 17:04:48 +0100605 //
606 // Note that the current speech item must be removed only if it
607 // belongs to the callingApp, else the item will be "orphaned" and
608 // not stopped correctly if a stop request comes along for the item
609 // from the app it belongs to.
Narayan Kamath492b7f02011-11-29 17:02:06 +0000610 SpeechItem current = maybeRemoveCurrentSpeechItem(callerIdentity);
Narayan Kamatha65c62a2011-09-06 17:04:48 +0100611 if (current != null) {
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000612 current.stop();
613 }
Narayan Kamath8d1fc242011-06-03 18:11:54 +0100614
Narayan Kamath4924fe32011-06-09 11:35:13 +0100615 // Remove any enqueued audio too.
Narayan Kamath67ae6bc2011-11-30 14:51:00 +0000616 mAudioPlaybackHandler.stopForApp(callerIdentity);
Narayan Kamath4924fe32011-06-09 11:35:13 +0100617
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000618 return TextToSpeech.SUCCESS;
619 }
Narayan Kamatha65c62a2011-09-06 17:04:48 +0100620
621 public int stopAll() {
Przemyslaw Szczepaniak13896b72012-11-09 15:18:16 +0000622 // Stop the current speech item unconditionally .
Narayan Kamatha65c62a2011-09-06 17:04:48 +0100623 SpeechItem current = setCurrentSpeechItem(null);
624 if (current != null) {
625 current.stop();
626 }
627 // Remove all other items from the queue.
628 removeCallbacksAndMessages(null);
629 // Remove all pending playback as well.
Narayan Kamath67ae6bc2011-11-30 14:51:00 +0000630 mAudioPlaybackHandler.stop();
Narayan Kamatha65c62a2011-09-06 17:04:48 +0100631
632 return TextToSpeech.SUCCESS;
633 }
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000634 }
635
Narayan Kamath754c72e2011-11-09 14:22:32 +0000636 interface UtteranceProgressDispatcher {
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +0100637 public void dispatchOnFallback();
638 public void dispatchOnStop();
639 public void dispatchOnSuccess();
Narayan Kamath754c72e2011-11-09 14:22:32 +0000640 public void dispatchOnStart();
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +0100641 public void dispatchOnError(int errorCode);
Narayan Kamath8d1fc242011-06-03 18:11:54 +0100642 }
643
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000644 /**
645 * An item in the synth thread queue.
646 */
Przemyslaw Szczepaniak13896b72012-11-09 15:18:16 +0000647 private abstract class SpeechItem {
Narayan Kamath492b7f02011-11-29 17:02:06 +0000648 private final Object mCallerIdentity;
Narayan Kamath492b7f02011-11-29 17:02:06 +0000649 private final int mCallerUid;
650 private final int mCallerPid;
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000651 private boolean mStarted = false;
652 private boolean mStopped = false;
653
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +0100654 public SpeechItem(Object caller, int callerUid, int callerPid) {
Narayan Kamath492b7f02011-11-29 17:02:06 +0000655 mCallerIdentity = caller;
Narayan Kamath492b7f02011-11-29 17:02:06 +0000656 mCallerUid = callerUid;
657 mCallerPid = callerPid;
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000658 }
659
Narayan Kamath492b7f02011-11-29 17:02:06 +0000660 public Object getCallerIdentity() {
661 return mCallerIdentity;
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000662 }
663
Przemyslaw Szczepaniak13896b72012-11-09 15:18:16 +0000664
665 public int getCallerUid() {
666 return mCallerUid;
667 }
668
669 public int getCallerPid() {
670 return mCallerPid;
671 }
672
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000673 /**
674 * Checker whether the item is valid. If this method returns false, the item should not
675 * be played.
676 */
677 public abstract boolean isValid();
678
679 /**
680 * Plays the speech item. Blocks until playback is finished.
681 * Must not be called more than once.
682 *
683 * Only called on the synthesis thread.
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000684 */
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +0100685 public void play() {
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000686 synchronized (this) {
687 if (mStarted) {
688 throw new IllegalStateException("play() called twice");
689 }
690 mStarted = true;
691 }
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +0100692 playImpl();
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000693 }
694
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +0100695 protected abstract void playImpl();
Przemyslaw Szczepaniak13896b72012-11-09 15:18:16 +0000696
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000697 /**
698 * Stops the speech item.
699 * Must not be called more than once.
700 *
701 * Can be called on multiple threads, but not on the synthesis thread.
702 */
703 public void stop() {
704 synchronized (this) {
705 if (mStopped) {
706 throw new IllegalStateException("stop() called twice");
707 }
708 mStopped = true;
709 }
710 stopImpl();
711 }
712
Przemyslaw Szczepaniak13896b72012-11-09 15:18:16 +0000713 protected abstract void stopImpl();
714
715 protected synchronized boolean isStopped() {
716 return mStopped;
717 }
718 }
719
720 /**
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +0100721 * An item in the synth thread queue that process utterance (and call back to client about
722 * progress).
Przemyslaw Szczepaniak13896b72012-11-09 15:18:16 +0000723 */
724 private abstract class UtteranceSpeechItem extends SpeechItem
725 implements UtteranceProgressDispatcher {
726
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +0100727 public UtteranceSpeechItem(Object caller, int callerUid, int callerPid) {
728 super(caller, callerUid, callerPid);
Przemyslaw Szczepaniak13896b72012-11-09 15:18:16 +0000729 }
730
Narayan Kamath754c72e2011-11-09 14:22:32 +0000731 @Override
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +0100732 public void dispatchOnSuccess() {
Narayan Kamath8d1fc242011-06-03 18:11:54 +0100733 final String utteranceId = getUtteranceId();
Narayan Kamath68e2af52011-11-28 17:10:04 +0000734 if (utteranceId != null) {
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +0100735 mCallbacks.dispatchOnSuccess(getCallerIdentity(), utteranceId);
736 }
737 }
738
739 @Override
740 public void dispatchOnStop() {
741 final String utteranceId = getUtteranceId();
742 if (utteranceId != null) {
743 mCallbacks.dispatchOnStop(getCallerIdentity(), utteranceId);
744 }
745 }
746
747 @Override
748 public void dispatchOnFallback() {
749 final String utteranceId = getUtteranceId();
750 if (utteranceId != null) {
751 mCallbacks.dispatchOnFallback(getCallerIdentity(), utteranceId);
Narayan Kamath754c72e2011-11-09 14:22:32 +0000752 }
753 }
754
755 @Override
756 public void dispatchOnStart() {
757 final String utteranceId = getUtteranceId();
Narayan Kamath68e2af52011-11-28 17:10:04 +0000758 if (utteranceId != null) {
Narayan Kamath492b7f02011-11-29 17:02:06 +0000759 mCallbacks.dispatchOnStart(getCallerIdentity(), utteranceId);
Narayan Kamath754c72e2011-11-09 14:22:32 +0000760 }
761 }
762
763 @Override
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +0100764 public void dispatchOnError(int errorCode) {
Narayan Kamath754c72e2011-11-09 14:22:32 +0000765 final String utteranceId = getUtteranceId();
Narayan Kamath68e2af52011-11-28 17:10:04 +0000766 if (utteranceId != null) {
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +0100767 mCallbacks.dispatchOnError(getCallerIdentity(), utteranceId, errorCode);
Narayan Kamath8d1fc242011-06-03 18:11:54 +0100768 }
769 }
770
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +0100771 abstract public String getUtteranceId();
772
773 String getStringParam(Bundle params, String key, String defaultValue) {
774 return params == null ? defaultValue : params.getString(key, defaultValue);
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000775 }
776
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +0100777 int getIntParam(Bundle params, String key, int defaultValue) {
778 return params == null ? defaultValue : params.getInt(key, defaultValue);
Bjorn Bringert50e657b2011-03-08 16:00:40 +0000779 }
780
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +0100781 float getFloatParam(Bundle params, String key, float defaultValue) {
782 return params == null ? defaultValue : params.getFloat(key, defaultValue);
783 }
784 }
785
786 /**
787 * UtteranceSpeechItem for V1 API speech items. V1 API speech items keep
788 * synthesis parameters in a single Bundle passed as parameter. This class
789 * allow subclasses to access them conveniently.
790 */
791 private abstract class SpeechItemV1 extends UtteranceSpeechItem {
792 protected final Bundle mParams;
793
794 SpeechItemV1(Object callerIdentity, int callerUid, int callerPid,
795 Bundle params) {
796 super(callerIdentity, callerUid, callerPid);
797 mParams = params;
798 }
799
800 boolean hasLanguage() {
801 return !TextUtils.isEmpty(getStringParam(mParams, Engine.KEY_PARAM_LANGUAGE, null));
802 }
803
804 int getSpeechRate() {
805 return getIntParam(mParams, Engine.KEY_PARAM_RATE, getDefaultSpeechRate());
806 }
807
808 int getPitch() {
809 return getIntParam(mParams, Engine.KEY_PARAM_PITCH, Engine.DEFAULT_PITCH);
810 }
811
812 @Override
813 public String getUtteranceId() {
814 return getStringParam(mParams, Engine.KEY_PARAM_UTTERANCE_ID, null);
815 }
816
817 int getStreamType() {
818 return getIntParam(mParams, Engine.KEY_PARAM_STREAM, Engine.DEFAULT_STREAM);
819 }
820
821 float getVolume() {
822 return getFloatParam(mParams, Engine.KEY_PARAM_VOLUME, Engine.DEFAULT_VOLUME);
823 }
824
825 float getPan() {
826 return getFloatParam(mParams, Engine.KEY_PARAM_PAN, Engine.DEFAULT_PAN);
827 }
828 }
829
830 class SynthesisSpeechItemV2 extends UtteranceSpeechItem {
831 private final SynthesisRequestV2 mSynthesisRequest;
832 private AbstractSynthesisCallback mSynthesisCallback;
833 private final EventLoggerV2 mEventLogger;
834
835 public SynthesisSpeechItemV2(Object callerIdentity, int callerUid, int callerPid,
836 SynthesisRequestV2 synthesisRequest) {
837 super(callerIdentity, callerUid, callerPid);
838
839 mSynthesisRequest = synthesisRequest;
840 mEventLogger = new EventLoggerV2(synthesisRequest, callerUid, callerPid,
841 mPackageName);
842
843 updateSpeechSpeedParam(synthesisRequest);
844 }
845
846 private void updateSpeechSpeedParam(SynthesisRequestV2 synthesisRequest) {
847 Bundle voiceParams = mSynthesisRequest.getVoiceParams();
848
849 // Inject default speech speed if needed
850 if (voiceParams.containsKey(TextToSpeechClient.Params.SPEECH_SPEED)) {
851 if (voiceParams.getFloat(TextToSpeechClient.Params.SPEECH_SPEED) <= 0) {
852 voiceParams.putFloat(TextToSpeechClient.Params.SPEECH_SPEED,
853 getDefaultSpeechRate() / 100.0f);
854 }
855 }
856 }
857
858 @Override
859 public boolean isValid() {
860 if (mSynthesisRequest.getText() == null) {
861 Log.e(TAG, "null synthesis text");
862 return false;
863 }
864 if (mSynthesisRequest.getText().length() >= TextToSpeech.getMaxSpeechInputLength()) {
865 Log.w(TAG, "Text too long: " + mSynthesisRequest.getText().length() + " chars");
866 return false;
867 }
868
869 return true;
870 }
871
872 @Override
873 protected void playImpl() {
874 AbstractSynthesisCallback synthesisCallback;
875 if (mEventLogger != null) {
876 mEventLogger.onRequestProcessingStart();
877 }
878 synchronized (this) {
879 // stop() might have been called before we enter this
880 // synchronized block.
881 if (isStopped()) {
882 return;
883 }
884 mSynthesisCallback = createSynthesisCallback();
885 synthesisCallback = mSynthesisCallback;
886 }
887
888 // Get voice info
Przemyslaw Szczepaniak97cd6472013-10-25 12:04:47 +0100889 VoiceInfo voiceInfo = getVoicesInfoWithName(mSynthesisRequest.getVoiceName());
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +0100890 if (voiceInfo != null) {
891 // Primary voice
892 TextToSpeechService.this.onSynthesizeTextV2(mSynthesisRequest, voiceInfo,
893 synthesisCallback);
894 } else {
Przemyslaw Szczepaniak97cd6472013-10-25 12:04:47 +0100895 Log.e(TAG, "Unknown voice name:" + mSynthesisRequest.getVoiceName());
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +0100896 synthesisCallback.error(TextToSpeechClient.Status.ERROR_INVALID_REQUEST);
897 }
898
899 // Fix for case where client called .start() & .error(), but did not called .done()
900 if (!synthesisCallback.hasFinished()) {
901 synthesisCallback.done();
902 }
903 }
904
905 @Override
906 protected void stopImpl() {
907 AbstractSynthesisCallback synthesisCallback;
908 synchronized (this) {
909 synthesisCallback = mSynthesisCallback;
910 }
911 if (synthesisCallback != null) {
912 // If the synthesis callback is null, it implies that we haven't
913 // entered the synchronized(this) block in playImpl which in
914 // turn implies that synthesis would not have started.
915 synthesisCallback.stop();
916 TextToSpeechService.this.onStop();
917 }
918 }
919
920 protected AbstractSynthesisCallback createSynthesisCallback() {
921 return new PlaybackSynthesisCallback(getStreamType(), getVolume(), getPan(),
922 mAudioPlaybackHandler, this, getCallerIdentity(), mEventLogger,
923 implementsV2API());
924 }
925
926 private int getStreamType() {
927 return getIntParam(mSynthesisRequest.getAudioParams(),
928 TextToSpeechClient.Params.AUDIO_PARAM_STREAM,
929 Engine.DEFAULT_STREAM);
930 }
931
932 private float getVolume() {
933 return getFloatParam(mSynthesisRequest.getAudioParams(),
934 TextToSpeechClient.Params.AUDIO_PARAM_VOLUME,
935 Engine.DEFAULT_VOLUME);
936 }
937
938 private float getPan() {
939 return getFloatParam(mSynthesisRequest.getAudioParams(),
940 TextToSpeechClient.Params.AUDIO_PARAM_PAN,
941 Engine.DEFAULT_PAN);
942 }
943
944 @Override
945 public String getUtteranceId() {
946 return mSynthesisRequest.getUtteranceId();
947 }
948 }
949
950 private class SynthesisToFileOutputStreamSpeechItemV2 extends SynthesisSpeechItemV2 {
951 private final FileOutputStream mFileOutputStream;
952
953 public SynthesisToFileOutputStreamSpeechItemV2(Object callerIdentity, int callerUid,
954 int callerPid,
955 SynthesisRequestV2 synthesisRequest,
956 FileOutputStream fileOutputStream) {
957 super(callerIdentity, callerUid, callerPid, synthesisRequest);
958 mFileOutputStream = fileOutputStream;
959 }
960
961 @Override
962 protected AbstractSynthesisCallback createSynthesisCallback() {
963 return new FileSynthesisCallback(mFileOutputStream.getChannel(),
964 this, getCallerIdentity(), implementsV2API());
965 }
966
967 @Override
968 protected void playImpl() {
969 super.playImpl();
970 try {
971 mFileOutputStream.close();
972 } catch(IOException e) {
973 Log.w(TAG, "Failed to close output file", e);
974 }
975 }
976 }
977
978 private class AudioSpeechItemV2 extends UtteranceSpeechItem {
979 private final AudioPlaybackQueueItem mItem;
980 private final Bundle mAudioParams;
981 private final String mUtteranceId;
982
983 public AudioSpeechItemV2(Object callerIdentity, int callerUid, int callerPid,
984 String utteranceId, Bundle audioParams, Uri uri) {
985 super(callerIdentity, callerUid, callerPid);
986 mUtteranceId = utteranceId;
987 mAudioParams = audioParams;
988 mItem = new AudioPlaybackQueueItem(this, getCallerIdentity(),
989 TextToSpeechService.this, uri, getStreamType());
990 }
991
992 @Override
993 public boolean isValid() {
994 return true;
995 }
996
997 @Override
998 protected void playImpl() {
999 mAudioPlaybackHandler.enqueue(mItem);
1000 }
1001
1002 @Override
1003 protected void stopImpl() {
1004 // Do nothing.
1005 }
1006
1007 protected int getStreamType() {
1008 return mAudioParams.getInt(TextToSpeechClient.Params.AUDIO_PARAM_STREAM);
Bjorn Bringert50e657b2011-03-08 16:00:40 +00001009 }
1010
1011 public String getUtteranceId() {
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +01001012 return mUtteranceId;
Bjorn Bringert50e657b2011-03-08 16:00:40 +00001013 }
Bjorn Bringert50e657b2011-03-08 16:00:40 +00001014 }
1015
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +01001016
1017 class SynthesisSpeechItemV1 extends SpeechItemV1 {
Narayan Kamath40f71f02011-11-23 16:42:53 +00001018 // Never null.
Bjorn Bringert50e657b2011-03-08 16:00:40 +00001019 private final String mText;
Narayan Kamathe22b69a2011-06-08 11:41:47 +01001020 private final SynthesisRequest mSynthesisRequest;
Narayan Kamathe5b8c4d2011-08-22 15:37:47 +01001021 private final String[] mDefaultLocale;
Narayan Kamathe22b69a2011-06-08 11:41:47 +01001022 // Non null after synthesis has started, and all accesses
1023 // guarded by 'this'.
1024 private AbstractSynthesisCallback mSynthesisCallback;
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +01001025 private final EventLoggerV1 mEventLogger;
Przemyslaw Szczepaniak65327832013-05-31 13:12:52 +01001026 private final int mCallerUid;
Bjorn Bringert50e657b2011-03-08 16:00:40 +00001027
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +01001028 public SynthesisSpeechItemV1(Object callerIdentity, int callerUid, int callerPid,
Narayan Kamath492b7f02011-11-29 17:02:06 +00001029 Bundle params, String text) {
1030 super(callerIdentity, callerUid, callerPid, params);
Bjorn Bringert50e657b2011-03-08 16:00:40 +00001031 mText = text;
Przemyslaw Szczepaniak65327832013-05-31 13:12:52 +01001032 mCallerUid = callerUid;
Narayan Kamathe22b69a2011-06-08 11:41:47 +01001033 mSynthesisRequest = new SynthesisRequest(mText, mParams);
Narayan Kamathe5b8c4d2011-08-22 15:37:47 +01001034 mDefaultLocale = getSettingsLocale();
Narayan Kamathe22b69a2011-06-08 11:41:47 +01001035 setRequestParams(mSynthesisRequest);
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +01001036 mEventLogger = new EventLoggerV1(mSynthesisRequest, callerUid, callerPid,
Narayan Kamath492b7f02011-11-29 17:02:06 +00001037 mPackageName);
Bjorn Bringert50e657b2011-03-08 16:00:40 +00001038 }
1039
1040 public String getText() {
1041 return mText;
1042 }
1043
1044 @Override
1045 public boolean isValid() {
Narayan Kamath40f71f02011-11-23 16:42:53 +00001046 if (mText == null) {
Narayan Kamath9c3d7a82012-07-20 18:01:43 +01001047 Log.e(TAG, "null synthesis text");
Bjorn Bringert50e657b2011-03-08 16:00:40 +00001048 return false;
1049 }
Przemyslaw Szczepaniak2d940bc2012-11-19 12:22:59 +00001050 if (mText.length() >= TextToSpeech.getMaxSpeechInputLength()) {
Bjorn Bringert50e657b2011-03-08 16:00:40 +00001051 Log.w(TAG, "Text too long: " + mText.length() + " chars");
1052 return false;
1053 }
1054 return true;
1055 }
1056
1057 @Override
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +01001058 protected void playImpl() {
Narayan Kamathe22b69a2011-06-08 11:41:47 +01001059 AbstractSynthesisCallback synthesisCallback;
Narayan Kamath6dabb632011-07-08 12:13:03 +01001060 mEventLogger.onRequestProcessingStart();
Bjorn Bringert50e657b2011-03-08 16:00:40 +00001061 synchronized (this) {
Narayan Kamatha65c62a2011-09-06 17:04:48 +01001062 // stop() might have been called before we enter this
1063 // synchronized block.
1064 if (isStopped()) {
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +01001065 return;
Narayan Kamatha65c62a2011-09-06 17:04:48 +01001066 }
Narayan Kamathe22b69a2011-06-08 11:41:47 +01001067 mSynthesisCallback = createSynthesisCallback();
1068 synthesisCallback = mSynthesisCallback;
Bjorn Bringert50e657b2011-03-08 16:00:40 +00001069 }
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +01001070
Narayan Kamathe22b69a2011-06-08 11:41:47 +01001071 TextToSpeechService.this.onSynthesizeText(mSynthesisRequest, synthesisCallback);
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +01001072
1073 // Fix for case where client called .start() & .error(), but did not called .done()
1074 if (synthesisCallback.hasStarted() && !synthesisCallback.hasFinished()) {
1075 synthesisCallback.done();
1076 }
Bjorn Bringert50e657b2011-03-08 16:00:40 +00001077 }
1078
Narayan Kamathe22b69a2011-06-08 11:41:47 +01001079 protected AbstractSynthesisCallback createSynthesisCallback() {
1080 return new PlaybackSynthesisCallback(getStreamType(), getVolume(), getPan(),
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +01001081 mAudioPlaybackHandler, this, getCallerIdentity(), mEventLogger, false);
Bjorn Bringert50e657b2011-03-08 16:00:40 +00001082 }
1083
1084 private void setRequestParams(SynthesisRequest request) {
Narayan Kamathc3edf2a2011-06-15 12:35:06 +01001085 request.setLanguage(getLanguage(), getCountry(), getVariant());
1086 request.setSpeechRate(getSpeechRate());
Przemyslaw Szczepaniak65327832013-05-31 13:12:52 +01001087 request.setCallerUid(mCallerUid);
Bjorn Bringert50e657b2011-03-08 16:00:40 +00001088 request.setPitch(getPitch());
1089 }
1090
1091 @Override
1092 protected void stopImpl() {
Narayan Kamathe22b69a2011-06-08 11:41:47 +01001093 AbstractSynthesisCallback synthesisCallback;
Bjorn Bringert50e657b2011-03-08 16:00:40 +00001094 synchronized (this) {
Narayan Kamathe22b69a2011-06-08 11:41:47 +01001095 synthesisCallback = mSynthesisCallback;
Bjorn Bringert50e657b2011-03-08 16:00:40 +00001096 }
Narayan Kamatha65c62a2011-09-06 17:04:48 +01001097 if (synthesisCallback != null) {
1098 // If the synthesis callback is null, it implies that we haven't
1099 // entered the synchronized(this) block in playImpl which in
1100 // turn implies that synthesis would not have started.
1101 synthesisCallback.stop();
1102 TextToSpeechService.this.onStop();
1103 }
Bjorn Bringert50e657b2011-03-08 16:00:40 +00001104 }
1105
Bjorn Bringert50e657b2011-03-08 16:00:40 +00001106 private String getCountry() {
Narayan Kamathe5b8c4d2011-08-22 15:37:47 +01001107 if (!hasLanguage()) return mDefaultLocale[1];
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +01001108 return getStringParam(mParams, Engine.KEY_PARAM_COUNTRY, "");
Bjorn Bringert50e657b2011-03-08 16:00:40 +00001109 }
1110
1111 private String getVariant() {
Narayan Kamathe5b8c4d2011-08-22 15:37:47 +01001112 if (!hasLanguage()) return mDefaultLocale[2];
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +01001113 return getStringParam(mParams, Engine.KEY_PARAM_VARIANT, "");
Bjorn Bringert50e657b2011-03-08 16:00:40 +00001114 }
1115
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +01001116 public String getLanguage() {
1117 return getStringParam(mParams, Engine.KEY_PARAM_LANGUAGE, mDefaultLocale[0]);
Bjorn Bringert50e657b2011-03-08 16:00:40 +00001118 }
1119 }
1120
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +01001121 private class SynthesisToFileOutputStreamSpeechItemV1 extends SynthesisSpeechItemV1 {
Przemyslaw Szczepaniakfcf671b2013-03-05 11:28:50 +00001122 private final FileOutputStream mFileOutputStream;
Bjorn Bringert50e657b2011-03-08 16:00:40 +00001123
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +01001124 public SynthesisToFileOutputStreamSpeechItemV1(Object callerIdentity, int callerUid,
Przemyslaw Szczepaniakfcf671b2013-03-05 11:28:50 +00001125 int callerPid, Bundle params, String text, FileOutputStream fileOutputStream) {
Narayan Kamath492b7f02011-11-29 17:02:06 +00001126 super(callerIdentity, callerUid, callerPid, params, text);
Przemyslaw Szczepaniakfcf671b2013-03-05 11:28:50 +00001127 mFileOutputStream = fileOutputStream;
Bjorn Bringert50e657b2011-03-08 16:00:40 +00001128 }
1129
1130 @Override
Narayan Kamathe22b69a2011-06-08 11:41:47 +01001131 protected AbstractSynthesisCallback createSynthesisCallback() {
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +01001132 return new FileSynthesisCallback(mFileOutputStream.getChannel(),
1133 this, getCallerIdentity(), false);
Bjorn Bringert50e657b2011-03-08 16:00:40 +00001134 }
1135
Narayan Kamath8d1fc242011-06-03 18:11:54 +01001136 @Override
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +01001137 protected void playImpl() {
Narayan Kamath754c72e2011-11-09 14:22:32 +00001138 dispatchOnStart();
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +01001139 super.playImpl();
Przemyslaw Szczepaniakfcf671b2013-03-05 11:28:50 +00001140 try {
1141 mFileOutputStream.close();
1142 } catch(IOException e) {
1143 Log.w(TAG, "Failed to close output file", e);
1144 }
Narayan Kamath8d1fc242011-06-03 18:11:54 +01001145 }
Bjorn Bringert50e657b2011-03-08 16:00:40 +00001146 }
1147
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +01001148 private class AudioSpeechItemV1 extends SpeechItemV1 {
Narayan Kamathaf802c62011-12-05 11:20:07 +00001149 private final AudioPlaybackQueueItem mItem;
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +01001150
1151 public AudioSpeechItemV1(Object callerIdentity, int callerUid, int callerPid,
Narayan Kamath492b7f02011-11-29 17:02:06 +00001152 Bundle params, Uri uri) {
1153 super(callerIdentity, callerUid, callerPid, params);
Narayan Kamathaf802c62011-12-05 11:20:07 +00001154 mItem = new AudioPlaybackQueueItem(this, getCallerIdentity(),
1155 TextToSpeechService.this, uri, getStreamType());
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 Kamathaf802c62011-12-05 11:20:07 +00001165 mAudioPlaybackHandler.enqueue(mItem);
Bjorn Bringert50e657b2011-03-08 16:00:40 +00001166 }
1167
1168 @Override
1169 protected void stopImpl() {
Narayan Kamathbe4ad4a2011-07-15 13:01:09 +01001170 // Do nothing.
Bjorn Bringert50e657b2011-03-08 16:00:40 +00001171 }
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +01001172
1173 @Override
1174 public String getUtteranceId() {
1175 return getStringParam(mParams, Engine.KEY_PARAM_UTTERANCE_ID, null);
1176 }
Bjorn Bringert50e657b2011-03-08 16:00:40 +00001177 }
1178
Przemyslaw Szczepaniak13896b72012-11-09 15:18:16 +00001179 private class SilenceSpeechItem extends UtteranceSpeechItem {
Bjorn Bringert50e657b2011-03-08 16:00:40 +00001180 private final long mDuration;
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +01001181 private final String mUtteranceId;
Bjorn Bringert50e657b2011-03-08 16:00:40 +00001182
Narayan Kamath492b7f02011-11-29 17:02:06 +00001183 public SilenceSpeechItem(Object callerIdentity, int callerUid, int callerPid,
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +01001184 String utteranceId, long duration) {
1185 super(callerIdentity, callerUid, callerPid);
1186 mUtteranceId = utteranceId;
Bjorn Bringert50e657b2011-03-08 16:00:40 +00001187 mDuration = duration;
Bjorn Bringert50e657b2011-03-08 16:00:40 +00001188 }
1189
1190 @Override
1191 public boolean isValid() {
1192 return true;
1193 }
1194
1195 @Override
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +01001196 protected void playImpl() {
Narayan Kamath67ae6bc2011-11-30 14:51:00 +00001197 mAudioPlaybackHandler.enqueue(new SilencePlaybackQueueItem(
1198 this, getCallerIdentity(), mDuration));
Bjorn Bringert50e657b2011-03-08 16:00:40 +00001199 }
1200
1201 @Override
1202 protected void stopImpl() {
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +01001203
1204 }
1205
1206 @Override
1207 public String getUtteranceId() {
1208 return mUtteranceId;
Bjorn Bringert50e657b2011-03-08 16:00:40 +00001209 }
1210 }
1211
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +01001212 /**
1213 * Call {@link TextToSpeechService#onVoicesInfoChange} on synthesis thread.
1214 */
1215 private class VoicesInfoChangeItem extends SpeechItem {
1216 public VoicesInfoChangeItem() {
1217 super(null, 0, 0); // It's never initiated by an user
1218 }
1219
1220 @Override
1221 public boolean isValid() {
1222 return true;
1223 }
1224
1225 @Override
1226 protected void playImpl() {
1227 TextToSpeechService.this.onVoicesInfoChange();
1228 }
1229
1230 @Override
1231 protected void stopImpl() {
1232 // No-op
1233 }
1234 }
1235
1236 /**
1237 * Call {@link TextToSpeechService#onLoadLanguage} on synth thread.
1238 */
Przemyslaw Szczepaniak13896b72012-11-09 15:18:16 +00001239 private class LoadLanguageItem extends SpeechItem {
1240 private final String mLanguage;
1241 private final String mCountry;
1242 private final String mVariant;
1243
1244 public LoadLanguageItem(Object callerIdentity, int callerUid, int callerPid,
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +01001245 String language, String country, String variant) {
1246 super(callerIdentity, callerUid, callerPid);
Przemyslaw Szczepaniak13896b72012-11-09 15:18:16 +00001247 mLanguage = language;
1248 mCountry = country;
1249 mVariant = variant;
1250 }
1251
1252 @Override
1253 public boolean isValid() {
1254 return true;
1255 }
1256
1257 @Override
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +01001258 protected void playImpl() {
1259 TextToSpeechService.this.onLoadLanguage(mLanguage, mCountry, mVariant);
Przemyslaw Szczepaniak13896b72012-11-09 15:18:16 +00001260 }
1261
1262 @Override
1263 protected void stopImpl() {
1264 // No-op
1265 }
1266 }
1267
Bjorn Bringert50e657b2011-03-08 16:00:40 +00001268 @Override
1269 public IBinder onBind(Intent intent) {
1270 if (TextToSpeech.Engine.INTENT_ACTION_TTS_SERVICE.equals(intent.getAction())) {
1271 return mBinder;
1272 }
1273 return null;
1274 }
1275
1276 /**
1277 * Binder returned from {@code #onBind(Intent)}. The methods in this class can be
1278 * called called from several different threads.
1279 */
Narayan Kamathabc63fb2011-06-10 11:36:57 +01001280 // NOTE: All calls that are passed in a calling app are interned so that
1281 // they can be used as message objects (which are tested for equality using ==).
Bjorn Bringert50e657b2011-03-08 16:00:40 +00001282 private final ITextToSpeechService.Stub mBinder = new ITextToSpeechService.Stub() {
Narayan Kamath492b7f02011-11-29 17:02:06 +00001283 @Override
1284 public int speak(IBinder caller, String text, int queueMode, Bundle params) {
1285 if (!checkNonNull(caller, text, params)) {
Narayan Kamathabc63fb2011-06-10 11:36:57 +01001286 return TextToSpeech.ERROR;
1287 }
1288
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +01001289 SpeechItem item = new SynthesisSpeechItemV1(caller,
Narayan Kamath492b7f02011-11-29 17:02:06 +00001290 Binder.getCallingUid(), Binder.getCallingPid(), params, text);
Bjorn Bringert50e657b2011-03-08 16:00:40 +00001291 return mSynthHandler.enqueueSpeechItem(queueMode, item);
1292 }
1293
Narayan Kamath492b7f02011-11-29 17:02:06 +00001294 @Override
Przemyslaw Szczepaniak5acb33a2013-02-08 16:36:25 +00001295 public int synthesizeToFileDescriptor(IBinder caller, String text, ParcelFileDescriptor
1296 fileDescriptor, Bundle params) {
1297 if (!checkNonNull(caller, text, fileDescriptor, params)) {
Narayan Kamathabc63fb2011-06-10 11:36:57 +01001298 return TextToSpeech.ERROR;
1299 }
1300
Przemyslaw Szczepaniak24943bf2013-03-13 15:45:28 +00001301 // In test env, ParcelFileDescriptor instance may be EXACTLY the same
1302 // one that is used by client. And it will be closed by a client, thus
1303 // preventing us from writing anything to it.
1304 final ParcelFileDescriptor sameFileDescriptor = ParcelFileDescriptor.adoptFd(
1305 fileDescriptor.detachFd());
1306
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +01001307 SpeechItem item = new SynthesisToFileOutputStreamSpeechItemV1(caller,
Przemyslaw Szczepaniakfcf671b2013-03-05 11:28:50 +00001308 Binder.getCallingUid(), Binder.getCallingPid(), params, text,
Przemyslaw Szczepaniak24943bf2013-03-13 15:45:28 +00001309 new ParcelFileDescriptor.AutoCloseOutputStream(sameFileDescriptor));
Bjorn Bringert50e657b2011-03-08 16:00:40 +00001310 return mSynthHandler.enqueueSpeechItem(TextToSpeech.QUEUE_ADD, item);
1311 }
1312
Narayan Kamath492b7f02011-11-29 17:02:06 +00001313 @Override
1314 public int playAudio(IBinder caller, Uri audioUri, int queueMode, Bundle params) {
1315 if (!checkNonNull(caller, audioUri, params)) {
Narayan Kamathabc63fb2011-06-10 11:36:57 +01001316 return TextToSpeech.ERROR;
1317 }
1318
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +01001319 SpeechItem item = new AudioSpeechItemV1(caller,
Narayan Kamath492b7f02011-11-29 17:02:06 +00001320 Binder.getCallingUid(), Binder.getCallingPid(), params, audioUri);
Bjorn Bringert50e657b2011-03-08 16:00:40 +00001321 return mSynthHandler.enqueueSpeechItem(queueMode, item);
1322 }
1323
Narayan Kamath492b7f02011-11-29 17:02:06 +00001324 @Override
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +01001325 public int playSilence(IBinder caller, long duration, int queueMode, String utteranceId) {
1326 if (!checkNonNull(caller)) {
Narayan Kamathabc63fb2011-06-10 11:36:57 +01001327 return TextToSpeech.ERROR;
1328 }
1329
Narayan Kamath492b7f02011-11-29 17:02:06 +00001330 SpeechItem item = new SilenceSpeechItem(caller,
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +01001331 Binder.getCallingUid(), Binder.getCallingPid(), utteranceId, duration);
Bjorn Bringert50e657b2011-03-08 16:00:40 +00001332 return mSynthHandler.enqueueSpeechItem(queueMode, item);
1333 }
1334
Narayan Kamath492b7f02011-11-29 17:02:06 +00001335 @Override
Bjorn Bringert50e657b2011-03-08 16:00:40 +00001336 public boolean isSpeaking() {
Narayan Kamathc34f76f2011-07-15 11:13:10 +01001337 return mSynthHandler.isSpeaking() || mAudioPlaybackHandler.isSpeaking();
Bjorn Bringert50e657b2011-03-08 16:00:40 +00001338 }
1339
Narayan Kamath492b7f02011-11-29 17:02:06 +00001340 @Override
1341 public int stop(IBinder caller) {
1342 if (!checkNonNull(caller)) {
Narayan Kamathabc63fb2011-06-10 11:36:57 +01001343 return TextToSpeech.ERROR;
1344 }
1345
Narayan Kamath492b7f02011-11-29 17:02:06 +00001346 return mSynthHandler.stopForApp(caller);
Bjorn Bringert50e657b2011-03-08 16:00:40 +00001347 }
1348
Narayan Kamath492b7f02011-11-29 17:02:06 +00001349 @Override
Bjorn Bringert50e657b2011-03-08 16:00:40 +00001350 public String[] getLanguage() {
1351 return onGetLanguage();
1352 }
1353
Przemyslaw Szczepaniakb4653372012-12-04 14:57:58 +00001354 @Override
1355 public String[] getClientDefaultLanguage() {
1356 return getSettingsLocale();
1357 }
1358
Narayan Kamath7a3af862011-06-02 17:28:57 +01001359 /*
1360 * If defaults are enforced, then no language is "available" except
1361 * perhaps the default language selected by the user.
1362 */
Narayan Kamath492b7f02011-11-29 17:02:06 +00001363 @Override
Bjorn Bringert50e657b2011-03-08 16:00:40 +00001364 public int isLanguageAvailable(String lang, String country, String variant) {
Narayan Kamathabc63fb2011-06-10 11:36:57 +01001365 if (!checkNonNull(lang)) {
1366 return TextToSpeech.ERROR;
1367 }
1368
Bjorn Bringert50e657b2011-03-08 16:00:40 +00001369 return onIsLanguageAvailable(lang, country, variant);
1370 }
1371
Narayan Kamath492b7f02011-11-29 17:02:06 +00001372 @Override
Narayan Kamath748af662011-10-31 14:20:01 +00001373 public String[] getFeaturesForLanguage(String lang, String country, String variant) {
1374 Set<String> features = onGetFeaturesForLanguage(lang, country, variant);
Narayan Kamath6c07a202011-11-07 14:21:39 +00001375 String[] featuresArray = null;
1376 if (features != null) {
1377 featuresArray = new String[features.size()];
1378 features.toArray(featuresArray);
1379 } else {
1380 featuresArray = new String[0];
1381 }
Narayan Kamath748af662011-10-31 14:20:01 +00001382 return featuresArray;
1383 }
1384
Narayan Kamath7a3af862011-06-02 17:28:57 +01001385 /*
1386 * There is no point loading a non default language if defaults
1387 * are enforced.
1388 */
Narayan Kamath492b7f02011-11-29 17:02:06 +00001389 @Override
Przemyslaw Szczepaniak13896b72012-11-09 15:18:16 +00001390 public int loadLanguage(IBinder caller, String lang, String country, String variant) {
Narayan Kamathabc63fb2011-06-10 11:36:57 +01001391 if (!checkNonNull(lang)) {
1392 return TextToSpeech.ERROR;
1393 }
Przemyslaw Szczepaniak13896b72012-11-09 15:18:16 +00001394 int retVal = onIsLanguageAvailable(lang, country, variant);
Narayan Kamathabc63fb2011-06-10 11:36:57 +01001395
Przemyslaw Szczepaniak13896b72012-11-09 15:18:16 +00001396 if (retVal == TextToSpeech.LANG_AVAILABLE ||
1397 retVal == TextToSpeech.LANG_COUNTRY_AVAILABLE ||
1398 retVal == TextToSpeech.LANG_COUNTRY_VAR_AVAILABLE) {
1399
1400 SpeechItem item = new LoadLanguageItem(caller, Binder.getCallingUid(),
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +01001401 Binder.getCallingPid(), lang, country, variant);
Przemyslaw Szczepaniak13896b72012-11-09 15:18:16 +00001402
1403 if (mSynthHandler.enqueueSpeechItem(TextToSpeech.QUEUE_ADD, item) !=
1404 TextToSpeech.SUCCESS) {
1405 return TextToSpeech.ERROR;
1406 }
1407 }
1408 return retVal;
Bjorn Bringert50e657b2011-03-08 16:00:40 +00001409 }
1410
Narayan Kamath492b7f02011-11-29 17:02:06 +00001411 @Override
1412 public void setCallback(IBinder caller, ITextToSpeechCallback cb) {
Narayan Kamathabc63fb2011-06-10 11:36:57 +01001413 // Note that passing in a null callback is a valid use case.
Narayan Kamath492b7f02011-11-29 17:02:06 +00001414 if (!checkNonNull(caller)) {
Narayan Kamathabc63fb2011-06-10 11:36:57 +01001415 return;
1416 }
1417
Narayan Kamath492b7f02011-11-29 17:02:06 +00001418 mCallbacks.setCallback(caller, cb);
Bjorn Bringert50e657b2011-03-08 16:00:40 +00001419 }
Narayan Kamath7a3af862011-06-02 17:28:57 +01001420
Narayan Kamathabc63fb2011-06-10 11:36:57 +01001421 private String intern(String in) {
1422 // The input parameter will be non null.
1423 return in.intern();
1424 }
1425
1426 private boolean checkNonNull(Object... args) {
1427 for (Object o : args) {
1428 if (o == null) return false;
1429 }
1430 return true;
1431 }
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +01001432
1433 @Override
1434 public List<VoiceInfo> getVoicesInfo() {
1435 return TextToSpeechService.this.getVoicesInfo();
1436 }
1437
1438 @Override
1439 public int speakV2(IBinder callingInstance,
1440 SynthesisRequestV2 request) {
1441 if (!checkNonNull(callingInstance, request)) {
1442 return TextToSpeech.ERROR;
1443 }
1444
1445 SpeechItem item = new SynthesisSpeechItemV2(callingInstance,
1446 Binder.getCallingUid(), Binder.getCallingPid(), request);
1447 return mSynthHandler.enqueueSpeechItem(TextToSpeech.QUEUE_ADD, item);
1448 }
1449
1450 @Override
1451 public int synthesizeToFileDescriptorV2(IBinder callingInstance,
1452 ParcelFileDescriptor fileDescriptor,
1453 SynthesisRequestV2 request) {
1454 if (!checkNonNull(callingInstance, request, fileDescriptor)) {
1455 return TextToSpeech.ERROR;
1456 }
1457
1458 // In test env, ParcelFileDescriptor instance may be EXACTLY the same
1459 // one that is used by client. And it will be closed by a client, thus
1460 // preventing us from writing anything to it.
1461 final ParcelFileDescriptor sameFileDescriptor = ParcelFileDescriptor.adoptFd(
1462 fileDescriptor.detachFd());
1463
1464 SpeechItem item = new SynthesisToFileOutputStreamSpeechItemV2(callingInstance,
1465 Binder.getCallingUid(), Binder.getCallingPid(), request,
1466 new ParcelFileDescriptor.AutoCloseOutputStream(sameFileDescriptor));
1467 return mSynthHandler.enqueueSpeechItem(TextToSpeech.QUEUE_ADD, item);
1468
1469 }
1470
1471 @Override
1472 public int playAudioV2(
1473 IBinder callingInstance, Uri audioUri, String utteranceId,
1474 Bundle systemParameters) {
1475 if (!checkNonNull(callingInstance, audioUri, systemParameters)) {
1476 return TextToSpeech.ERROR;
1477 }
1478
1479 SpeechItem item = new AudioSpeechItemV2(callingInstance,
1480 Binder.getCallingUid(), Binder.getCallingPid(), utteranceId, systemParameters,
1481 audioUri);
1482 return mSynthHandler.enqueueSpeechItem(TextToSpeech.QUEUE_ADD, item);
1483 }
Bjorn Bringert50e657b2011-03-08 16:00:40 +00001484 };
1485
1486 private class CallbackMap extends RemoteCallbackList<ITextToSpeechCallback> {
Narayan Kamath492b7f02011-11-29 17:02:06 +00001487 private final HashMap<IBinder, ITextToSpeechCallback> mCallerToCallback
1488 = new HashMap<IBinder, ITextToSpeechCallback>();
Bjorn Bringert50e657b2011-03-08 16:00:40 +00001489
Narayan Kamath492b7f02011-11-29 17:02:06 +00001490 public void setCallback(IBinder caller, ITextToSpeechCallback cb) {
1491 synchronized (mCallerToCallback) {
Bjorn Bringert50e657b2011-03-08 16:00:40 +00001492 ITextToSpeechCallback old;
1493 if (cb != null) {
Narayan Kamath492b7f02011-11-29 17:02:06 +00001494 register(cb, caller);
1495 old = mCallerToCallback.put(caller, cb);
Bjorn Bringert50e657b2011-03-08 16:00:40 +00001496 } else {
Narayan Kamath492b7f02011-11-29 17:02:06 +00001497 old = mCallerToCallback.remove(caller);
Bjorn Bringert50e657b2011-03-08 16:00:40 +00001498 }
1499 if (old != null && old != cb) {
1500 unregister(old);
1501 }
1502 }
1503 }
1504
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +01001505 public void dispatchOnFallback(Object callerIdentity, String utteranceId) {
Narayan Kamath492b7f02011-11-29 17:02:06 +00001506 ITextToSpeechCallback cb = getCallbackFor(callerIdentity);
Bjorn Bringert50e657b2011-03-08 16:00:40 +00001507 if (cb == null) return;
1508 try {
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +01001509 cb.onFallback(utteranceId);
1510 } catch (RemoteException e) {
1511 Log.e(TAG, "Callback onFallback failed: " + e);
1512 }
1513 }
1514
1515 public void dispatchOnStop(Object callerIdentity, String utteranceId) {
1516 ITextToSpeechCallback cb = getCallbackFor(callerIdentity);
1517 if (cb == null) return;
1518 try {
1519 cb.onStop(utteranceId);
1520 } catch (RemoteException e) {
1521 Log.e(TAG, "Callback onStop failed: " + e);
1522 }
1523 }
1524
1525 public void dispatchOnSuccess(Object callerIdentity, String utteranceId) {
1526 ITextToSpeechCallback cb = getCallbackFor(callerIdentity);
1527 if (cb == null) return;
1528 try {
1529 cb.onSuccess(utteranceId);
Bjorn Bringert50e657b2011-03-08 16:00:40 +00001530 } catch (RemoteException e) {
Narayan Kamath754c72e2011-11-09 14:22:32 +00001531 Log.e(TAG, "Callback onDone failed: " + e);
1532 }
1533 }
1534
Narayan Kamath492b7f02011-11-29 17:02:06 +00001535 public void dispatchOnStart(Object callerIdentity, String utteranceId) {
1536 ITextToSpeechCallback cb = getCallbackFor(callerIdentity);
Narayan Kamath754c72e2011-11-09 14:22:32 +00001537 if (cb == null) return;
1538 try {
1539 cb.onStart(utteranceId);
1540 } catch (RemoteException e) {
1541 Log.e(TAG, "Callback onStart failed: " + e);
1542 }
1543
1544 }
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
1557 @Override
1558 public void onCallbackDied(ITextToSpeechCallback callback, Object cookie) {
Narayan Kamath492b7f02011-11-29 17:02:06 +00001559 IBinder caller = (IBinder) cookie;
1560 synchronized (mCallerToCallback) {
1561 mCallerToCallback.remove(caller);
Bjorn Bringert50e657b2011-03-08 16:00:40 +00001562 }
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +01001563 //mSynthHandler.stopForApp(caller);
Bjorn Bringert50e657b2011-03-08 16:00:40 +00001564 }
1565
1566 @Override
1567 public void kill() {
Narayan Kamath492b7f02011-11-29 17:02:06 +00001568 synchronized (mCallerToCallback) {
1569 mCallerToCallback.clear();
Bjorn Bringert50e657b2011-03-08 16:00:40 +00001570 super.kill();
1571 }
1572 }
1573
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +01001574 public void dispatchVoicesInfoChange(List<VoiceInfo> voicesInfo) {
1575 synchronized (mCallerToCallback) {
1576 for (ITextToSpeechCallback callback : mCallerToCallback.values()) {
1577 try {
1578 callback.onVoicesInfoChange(voicesInfo);
1579 } catch (RemoteException e) {
1580 Log.e(TAG, "Failed to request reconnect", e);
1581 }
1582 }
1583 }
1584 }
1585
Narayan Kamath492b7f02011-11-29 17:02:06 +00001586 private ITextToSpeechCallback getCallbackFor(Object caller) {
Narayan Kamath754c72e2011-11-09 14:22:32 +00001587 ITextToSpeechCallback cb;
Narayan Kamath492b7f02011-11-29 17:02:06 +00001588 IBinder asBinder = (IBinder) caller;
1589 synchronized (mCallerToCallback) {
1590 cb = mCallerToCallback.get(asBinder);
Narayan Kamath754c72e2011-11-09 14:22:32 +00001591 }
1592
1593 return cb;
1594 }
Bjorn Bringert50e657b2011-03-08 16:00:40 +00001595 }
Bjorn Bringert50e657b2011-03-08 16:00:40 +00001596}