blob: c6a14f2937dd2b6049988277b426dc71c0cae547 [file] [log] [blame]
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +01001/*
2 * Copyright (C) 2013 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 */
16
17package android.speech.tts;
18
19import android.app.Activity;
20import android.app.Application;
21import android.content.ComponentName;
22import android.content.Context;
23import android.content.Intent;
24import android.content.ServiceConnection;
25import android.media.AudioManager;
26import android.net.Uri;
27import android.os.AsyncTask;
28import android.os.IBinder;
29import android.os.ParcelFileDescriptor;
30import android.os.RemoteException;
31import android.speech.tts.ITextToSpeechCallback;
32import android.speech.tts.ITextToSpeechService;
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +010033import android.util.Log;
34import android.util.Pair;
35
36import java.io.File;
37import java.io.FileNotFoundException;
38import java.io.IOException;
39import java.util.Collections;
40import java.util.HashMap;
41import java.util.List;
Przemyslaw Szczepaniak1ca1d8862014-01-29 15:20:06 +000042import java.util.concurrent.atomic.AtomicInteger;
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +010043
44/**
45 * Synthesizes speech from text for immediate playback or to create a sound
46 * file.
47 * <p>
48 * This is an updated version of the speech synthesis client that supersedes
49 * {@link android.speech.tts.TextToSpeech}.
50 * <p>
51 * A TextToSpeechClient instance can only be used to synthesize text once it has
52 * connected to the service. The TextToSpeechClient instance will start establishing
53 * the connection after a call to the {@link #connect()} method. This is usually done in
54 * {@link Application#onCreate()} or {@link Activity#onCreate}. When the connection
55 * is established, the instance will call back using the
56 * {@link TextToSpeechClient.ConnectionCallbacks} interface. Only after a
57 * successful callback is the client usable.
58 * <p>
59 * After successful connection, the list of all available voices can be obtained
60 * by calling the {@link TextToSpeechClient#getEngineStatus() method. The client can
61 * choose a voice using some custom heuristic and build a {@link RequestConfig} object
62 * using {@link RequestConfig.Builder}, or can use one of the common heuristics found
63 * in ({@link RequestConfigHelper}.
64 * <p>
65 * When you are done using the TextToSpeechClient instance, call the
66 * {@link #disconnect()} method to release the connection.
67 * <p>
68 * In the rare case of a change to the set of available voices, the service will call to the
69 * {@link ConnectionCallbacks#onEngineStatusChange} with new set of available voices as argument.
70 * In response, the client HAVE to recreate all {@link RequestConfig} instances in use.
71 */
72public final class TextToSpeechClient {
73 private static final String TAG = TextToSpeechClient.class.getSimpleName();
74
75 private final Object mLock = new Object();
76 private final TtsEngines mEnginesHelper;
77 private final Context mContext;
78
79 // Guarded by mLock
80 private Connection mServiceConnection;
81 private final RequestCallbacks mDefaultRequestCallbacks;
82 private final ConnectionCallbacks mConnectionCallbacks;
83 private EngineStatus mEngineStatus;
84 private String mRequestedEngine;
85 private boolean mFallbackToDefault;
86 private HashMap<String, Pair<UtteranceId, RequestCallbacks>> mCallbacks;
87 // Guarded by mLock
88
89 /** Common voices parameters */
90 public static final class Params {
91 private Params() {}
92
93 /**
94 * Maximum allowed time for a single request attempt, in milliseconds, before synthesis
95 * fails (or fallback request starts, if requested using
Przemyslaw Szczepaniak97cd6472013-10-25 12:04:47 +010096 * {@link #FALLBACK_VOICE_NAME}).
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +010097 */
98 public static final String NETWORK_TIMEOUT_MS = "networkTimeoutMs";
99
100 /**
101 * Number of network request retries that are attempted in case of failure
102 */
103 public static final String NETWORK_RETRIES_COUNT = "networkRetriesCount";
104
105 /**
106 * Should synthesizer report sub-utterance progress on synthesis. Only applicable
107 * for the {@link TextToSpeechClient#queueSpeak} method.
108 */
109 public static final String TRACK_SUBUTTERANCE_PROGRESS = "trackSubutteranceProgress";
110
111 /**
112 * If a voice exposes this parameter then it supports the fallback request feature.
113 *
Przemyslaw Szczepaniak97cd6472013-10-25 12:04:47 +0100114 * If it is set to a valid name of some other voice ({@link VoiceInfo#getName()}) then
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +0100115 * in case of request failure (due to network problems or missing data), fallback request
116 * will be attempted. Request will be done using the voice referenced by this parameter.
117 * If it is the case, the client will be informed by a callback to the {@link
118 * RequestCallbacks#onSynthesisFallback(UtteranceId)}.
119 */
Przemyslaw Szczepaniak97cd6472013-10-25 12:04:47 +0100120 public static final String FALLBACK_VOICE_NAME = "fallbackVoiceName";
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +0100121
122 /**
123 * Audio parameter for specifying a linear multiplier to the speaking speed of the voice.
124 * The value is a float. Values below zero decrease speed of the synthesized speech
125 * values above one increase it. If the value of this parameter is equal to zero,
126 * then it will be replaced by a settings-configurable default before it reaches
127 * TTS service.
128 */
129 public static final String SPEECH_SPEED = "speechSpeed";
130
131 /**
132 * Audio parameter for controlling the pitch of the output. The Value is a positive float,
133 * with default of {@code 1.0}. The value is used to scale the primary frequency linearly.
134 * Lower values lower the tone of the synthesized voice, greater values increase it.
135 */
136 public static final String SPEECH_PITCH = "speechPitch";
137
138 /**
139 * Audio parameter for controlling output volume. Value is a float with scale of 0 to 1
140 */
141 public static final String AUDIO_PARAM_VOLUME = TextToSpeech.Engine.KEY_PARAM_VOLUME;
142
143 /**
144 * Audio parameter for controlling output pan.
145 * Value is a float ranging from -1 to +1 where -1 maps to a hard-left pan,
146 * 0 to center (the default behavior), and +1 to hard-right.
147 */
148 public static final String AUDIO_PARAM_PAN = TextToSpeech.Engine.KEY_PARAM_PAN;
149
150 /**
151 * Audio parameter for specifying the audio stream type to be used when speaking text
152 * or playing back a file. The value should be one of the STREAM_ constants
153 * defined in {@link AudioManager}.
154 */
155 public static final String AUDIO_PARAM_STREAM = TextToSpeech.Engine.KEY_PARAM_STREAM;
156 }
157
158 /**
159 * Result codes for TTS operations.
160 */
161 public static final class Status {
162 private Status() {}
163
164 /**
165 * Denotes a successful operation.
166 */
167 public static final int SUCCESS = 0;
168
169 /**
170 * Denotes a stop requested by a client. It's used only on the service side of the API,
171 * client should never expect to see this result code.
172 */
173 public static final int STOPPED = 100;
174
175 /**
176 * Denotes a generic failure.
177 */
178 public static final int ERROR_UNKNOWN = -1;
179
180 /**
181 * Denotes a failure of a TTS engine to synthesize the given input.
182 */
183 public static final int ERROR_SYNTHESIS = 10;
184
185 /**
186 * Denotes a failure of a TTS service.
187 */
188 public static final int ERROR_SERVICE = 11;
189
190 /**
191 * Denotes a failure related to the output (audio device or a file).
192 */
193 public static final int ERROR_OUTPUT = 12;
194
195 /**
196 * Denotes a failure caused by a network connectivity problems.
197 */
198 public static final int ERROR_NETWORK = 13;
199
200 /**
201 * Denotes a failure caused by network timeout.
202 */
203 public static final int ERROR_NETWORK_TIMEOUT = 14;
204
205 /**
206 * Denotes a failure caused by an invalid request.
207 */
208 public static final int ERROR_INVALID_REQUEST = 15;
209
210 /**
211 * Denotes a failure related to passing a non-unique utterance id.
212 */
213 public static final int ERROR_NON_UNIQUE_UTTERANCE_ID = 16;
214
215 /**
216 * Denotes a failure related to missing data. The TTS implementation may download
217 * the missing data, and if so, request will succeed in future. This error can only happen
218 * for voices with {@link VoiceInfo#FEATURE_MAY_AUTOINSTALL} feature.
219 * Note: the recommended way to avoid this error is to create a request with the fallback
220 * voice.
221 */
222 public static final int ERROR_DOWNLOADING_ADDITIONAL_DATA = 17;
223 }
224
225 /**
226 * Set of callbacks for the events related to the progress of a synthesis request
227 * through the synthesis queue. Each synthesis request is associated with a call to
228 * {@link #queueSpeak} or {@link #queueSynthesizeToFile}.
229 *
230 * The callbacks specified in this method will NOT be called on UI thread.
231 */
232 public static abstract class RequestCallbacks {
233 /**
234 * Called after synthesis of utterance successfully starts.
235 */
236 public void onSynthesisStart(UtteranceId utteranceId) {}
237
238 /**
239 * Called after synthesis successfully finishes.
240 * @param utteranceId
241 * Unique identifier of synthesized utterance.
242 */
243 public void onSynthesisSuccess(UtteranceId utteranceId) {}
244
245 /**
246 * Called after synthesis was stopped in middle of synthesis process.
247 * @param utteranceId
248 * Unique identifier of synthesized utterance.
249 */
250 public void onSynthesisStop(UtteranceId utteranceId) {}
251
252 /**
253 * Called when requested synthesis failed and fallback synthesis is about to be attempted.
254 *
Przemyslaw Szczepaniak97cd6472013-10-25 12:04:47 +0100255 * Requires voice with available {@link TextToSpeechClient.Params#FALLBACK_VOICE_NAME}
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +0100256 * parameter, and request with this parameter enabled.
257 *
258 * This callback will be followed by callback to the {@link #onSynthesisStart},
259 * {@link #onSynthesisFailure} or {@link #onSynthesisSuccess} that depends on the
260 * fallback outcome.
261 *
262 * For more fallback feature reference, look at the
Przemyslaw Szczepaniak97cd6472013-10-25 12:04:47 +0100263 * {@link TextToSpeechClient.Params#FALLBACK_VOICE_NAME}.
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +0100264 *
265 * @param utteranceId
266 * Unique identifier of synthesized utterance.
267 */
268 public void onSynthesisFallback(UtteranceId utteranceId) {}
269
270 /**
271 * Called after synthesis of utterance fails.
272 *
273 * It may be called instead or after a {@link #onSynthesisStart} callback.
274 *
275 * @param utteranceId
276 * Unique identifier of synthesized utterance.
277 * @param errorCode
278 * One of the values from {@link Status}.
279 */
280 public void onSynthesisFailure(UtteranceId utteranceId, int errorCode) {}
281
282 /**
283 * Called during synthesis to mark synthesis progress.
284 *
285 * Requires voice with available
286 * {@link TextToSpeechClient.Params#TRACK_SUBUTTERANCE_PROGRESS} parameter, and
287 * request with this parameter enabled.
288 *
289 * @param utteranceId
290 * Unique identifier of synthesized utterance.
291 * @param charIndex
292 * String index (java char offset) of recently synthesized character.
293 * @param msFromStart
294 * Miliseconds from the start of the synthesis.
295 */
Przemyslaw Szczepaniak1ca1d8862014-01-29 15:20:06 +0000296 public void onSynthesisProgress(UtteranceId utteranceId, int charIndex,
297 int msFromStart) {}
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +0100298 }
299
300 /**
301 * Interface definition of callbacks that are called when the client is
302 * connected or disconnected from the TTS service.
303 */
304 public static interface ConnectionCallbacks {
305 /**
306 * After calling {@link TextToSpeechClient#connect()}, this method will be invoked
307 * asynchronously when the connect request has successfully completed.
308 *
309 * Clients are strongly encouraged to call {@link TextToSpeechClient#getEngineStatus()}
310 * and create {@link RequestConfig} objects used in subsequent synthesis requests.
311 */
312 public void onConnectionSuccess();
313
314 /**
315 * After calling {@link TextToSpeechClient#connect()}, this method may be invoked
316 * asynchronously when the connect request has failed to complete.
317 *
318 * It may be also invoked synchronously, from the body of
319 * {@link TextToSpeechClient#connect()} method.
320 */
321 public void onConnectionFailure();
322
323 /**
324 * Called when the connection to the service is lost. This can happen if there is a problem
325 * with the speech service (e.g. a crash or resource problem causes it to be killed by the
326 * system). When called, all requests have been canceled and no outstanding listeners will
327 * be executed. Applications should disable UI components that require the service.
328 */
329 public void onServiceDisconnected();
330
331 /**
332 * After receiving {@link #onConnectionSuccess()} callback, this method may be invoked
333 * if engine status obtained from {@link TextToSpeechClient#getEngineStatus()}) changes.
334 * It usually means that some voices were removed, changed or added.
335 *
336 * Clients are required to recreate {@link RequestConfig} objects used in subsequent
337 * synthesis requests.
338 */
339 public void onEngineStatusChange(EngineStatus newEngineStatus);
340 }
341
342 /** State of voices as provided by engine and user. */
343 public static final class EngineStatus {
344 /** All available voices. */
345 private final List<VoiceInfo> mVoices;
346
347 /** Name of the TTS engine package */
348 private final String mPackageName;
349
350 private EngineStatus(String packageName, List<VoiceInfo> voices) {
351 this.mVoices = Collections.unmodifiableList(voices);
352 this.mPackageName = packageName;
353 }
354
355 /**
356 * Get an immutable list of all Voices exposed by the TTS engine.
357 */
358 public List<VoiceInfo> getVoices() {
359 return mVoices;
360 }
361
362 /**
363 * Get name of the TTS engine package currently in use.
364 */
365 public String getEnginePackage() {
366 return mPackageName;
367 }
368 }
369
370 /** Unique synthesis request identifier. */
Przemyslaw Szczepaniak1ca1d8862014-01-29 15:20:06 +0000371 public static class UtteranceId {
372 /** Unique identifier */
373 private final int id;
374
375 /** Unique identifier generator */
376 private static final AtomicInteger ID_GENERATOR = new AtomicInteger();
377
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +0100378 /**
379 * Create new, unique UtteranceId instance.
380 */
381 public UtteranceId() {
Przemyslaw Szczepaniak1ca1d8862014-01-29 15:20:06 +0000382 id = ID_GENERATOR.getAndIncrement();
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +0100383 }
384
385 /**
386 * Returns a unique string associated with an instance of this object.
387 *
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +0100388 * This string will be used to identify the synthesis request/utterance inside the
389 * TTS service.
390 */
Przemyslaw Szczepaniak1ca1d8862014-01-29 15:20:06 +0000391 public final String toUniqueString() {
392 return "UID" + id;
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +0100393 }
394 }
395
396 /**
397 * Create TextToSpeech service client.
398 *
399 * Will connect to the default TTS service. In order to be usable, {@link #connect()} need
400 * to be called first and successful connection callback need to be received.
401 *
402 * @param context
403 * The context this instance is running in.
404 * @param engine
405 * Package name of requested TTS engine. If it's null, then default engine will
406 * be selected regardless of {@code fallbackToDefaultEngine} parameter value.
407 * @param fallbackToDefaultEngine
408 * If requested engine is not available, should we fallback to the default engine?
409 * @param defaultRequestCallbacks
410 * Default request callbacks, it will be used for all synthesis requests without
411 * supplied RequestCallbacks instance. Can't be null.
412 * @param connectionCallbacks
413 * Callbacks for connecting and disconnecting from the service. Can't be null.
414 */
415 public TextToSpeechClient(Context context,
416 String engine, boolean fallbackToDefaultEngine,
417 RequestCallbacks defaultRequestCallbacks,
418 ConnectionCallbacks connectionCallbacks) {
419 if (context == null)
420 throw new IllegalArgumentException("context can't be null");
421 if (defaultRequestCallbacks == null)
422 throw new IllegalArgumentException("defaultRequestCallbacks can't be null");
423 if (connectionCallbacks == null)
424 throw new IllegalArgumentException("connectionCallbacks can't be null");
425 mContext = context;
426 mEnginesHelper = new TtsEngines(mContext);
427 mCallbacks = new HashMap<String, Pair<UtteranceId, RequestCallbacks>>();
428 mDefaultRequestCallbacks = defaultRequestCallbacks;
429 mConnectionCallbacks = connectionCallbacks;
430
431 mRequestedEngine = engine;
432 mFallbackToDefault = fallbackToDefaultEngine;
433 }
434
435 /**
436 * Create TextToSpeech service client. Will connect to the default TTS
437 * service. In order to be usable, {@link #connect()} need to be called
438 * first and successful connection callback need to be received.
439 *
440 * @param context Context this instance is running in.
441 * @param defaultRequestCallbacks Default request callbacks, it
442 * will be used for all synthesis requests without supplied
443 * RequestCallbacks instance. Can't be null.
444 * @param connectionCallbacks Callbacks for connecting and disconnecting
445 * from the service. Can't be null.
446 */
447 public TextToSpeechClient(Context context, RequestCallbacks defaultRequestCallbacks,
448 ConnectionCallbacks connectionCallbacks) {
449 this(context, null, true, defaultRequestCallbacks, connectionCallbacks);
450 }
451
452
453 private boolean initTts(String requestedEngine, boolean fallbackToDefaultEngine) {
454 // Step 1: Try connecting to the engine that was requested.
455 if (requestedEngine != null) {
456 if (mEnginesHelper.isEngineInstalled(requestedEngine)) {
457 if ((mServiceConnection = connectToEngine(requestedEngine)) != null) {
458 return true;
459 } else if (!fallbackToDefaultEngine) {
460 Log.w(TAG, "Couldn't connect to requested engine: " + requestedEngine);
461 return false;
462 }
463 } else if (!fallbackToDefaultEngine) {
464 Log.w(TAG, "Requested engine not installed: " + requestedEngine);
465 return false;
466 }
467 }
468
469 // Step 2: Try connecting to the user's default engine.
470 final String defaultEngine = mEnginesHelper.getDefaultEngine();
471 if (defaultEngine != null && !defaultEngine.equals(requestedEngine)) {
472 if ((mServiceConnection = connectToEngine(defaultEngine)) != null) {
473 return true;
474 }
475 }
476
477 // Step 3: Try connecting to the highest ranked engine in the
478 // system.
479 final String highestRanked = mEnginesHelper.getHighestRankedEngineName();
480 if (highestRanked != null && !highestRanked.equals(requestedEngine) &&
481 !highestRanked.equals(defaultEngine)) {
482 if ((mServiceConnection = connectToEngine(highestRanked)) != null) {
483 return true;
484 }
485 }
486
487 Log.w(TAG, "Couldn't find working TTS engine");
488 return false;
489 }
490
491 private Connection connectToEngine(String engine) {
492 Connection connection = new Connection(engine);
493 Intent intent = new Intent(TextToSpeech.Engine.INTENT_ACTION_TTS_SERVICE);
494 intent.setPackage(engine);
495 boolean bound = mContext.bindService(intent, connection, Context.BIND_AUTO_CREATE);
496 if (!bound) {
497 Log.e(TAG, "Failed to bind to " + engine);
498 return null;
499 } else {
500 Log.i(TAG, "Successfully bound to " + engine);
501 return connection;
502 }
503 }
504
505
506 /**
507 * Connects the client to TTS service. This method returns immediately, and connects to the
508 * service in the background.
509 *
510 * After connection initializes successfully, {@link ConnectionCallbacks#onConnectionSuccess()}
511 * is called. On a failure {@link ConnectionCallbacks#onConnectionFailure} is called.
512 *
513 * Both of those callback may be called asynchronously on the main thread,
514 * {@link ConnectionCallbacks#onConnectionFailure} may be called synchronously, before
515 * this method returns.
516 */
517 public void connect() {
518 synchronized (mLock) {
519 if (mServiceConnection != null) {
520 return;
521 }
522 if(!initTts(mRequestedEngine, mFallbackToDefault)) {
523 mConnectionCallbacks.onConnectionFailure();
524 }
525 }
526 }
527
528 /**
529 * Checks if the client is currently connected to the service, so that
530 * requests to other methods will succeed.
531 */
532 public boolean isConnected() {
533 synchronized (mLock) {
534 return mServiceConnection != null && mServiceConnection.isEstablished();
535 }
536 }
537
538 /**
539 * Closes the connection to TextToSpeech service. No calls can be made on this object after
540 * calling this method.
541 * It is good practice to call this method in the onDestroy() method of an Activity
542 * so the TextToSpeech engine can be cleanly stopped.
543 */
544 public void disconnect() {
545 synchronized (mLock) {
546 if (mServiceConnection != null) {
547 mServiceConnection.disconnect();
548 mServiceConnection = null;
549 mCallbacks.clear();
550 }
551 }
552 }
553
554 /**
555 * Register callback.
556 *
557 * @param utteranceId Non-null UtteranceId instance.
558 * @param callback Non-null callbacks for the request
559 * @return Status.SUCCESS or error code in case of invalid arguments.
560 */
561 private int addCallback(UtteranceId utteranceId, RequestCallbacks callback) {
562 synchronized (mLock) {
563 if (utteranceId == null || callback == null) {
564 return Status.ERROR_INVALID_REQUEST;
565 }
566 if (mCallbacks.put(utteranceId.toUniqueString(),
567 new Pair<UtteranceId, RequestCallbacks>(utteranceId, callback)) != null) {
568 return Status.ERROR_NON_UNIQUE_UTTERANCE_ID;
569 }
570 return Status.SUCCESS;
571 }
572 }
573
574 /**
575 * Remove and return callback.
576 *
577 * @param utteranceIdStr Unique string obtained from {@link UtteranceId#toUniqueString}.
578 */
579 private Pair<UtteranceId, RequestCallbacks> removeCallback(String utteranceIdStr) {
580 synchronized (mLock) {
581 return mCallbacks.remove(utteranceIdStr);
582 }
583 }
584
585 /**
586 * Get callback and utterance id.
587 *
588 * @param utteranceIdStr Unique string obtained from {@link UtteranceId#toUniqueString}.
589 */
590 private Pair<UtteranceId, RequestCallbacks> getCallback(String utteranceIdStr) {
591 synchronized (mLock) {
592 return mCallbacks.get(utteranceIdStr);
593 }
594 }
595
596 /**
597 * Remove callback and call {@link RequestCallbacks#onSynthesisFailure} with passed
598 * error code.
599 *
600 * @param utteranceIdStr Unique string obtained from {@link UtteranceId#toUniqueString}.
601 * @param errorCode argument to {@link RequestCallbacks#onSynthesisFailure} call.
602 */
603 private void removeCallbackAndErr(String utteranceIdStr, int errorCode) {
604 synchronized (mLock) {
605 Pair<UtteranceId, RequestCallbacks> c = mCallbacks.remove(utteranceIdStr);
606 c.second.onSynthesisFailure(c.first, errorCode);
607 }
608 }
609
610 /**
Nick Kralevicheb337052013-10-24 15:56:04 -0700611 * Retrieve TTS engine status {@link EngineStatus}. Requires connected client.
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +0100612 */
613 public EngineStatus getEngineStatus() {
614 synchronized (mLock) {
615 return mEngineStatus;
616 }
617 }
618
619 /**
620 * Query TTS engine about available voices and defaults.
621 *
622 * @return EngineStatus is connected or null if client is disconnected.
623 */
624 private EngineStatus requestEngineStatus(ITextToSpeechService service)
625 throws RemoteException {
626 List<VoiceInfo> voices = service.getVoicesInfo();
627 if (voices == null) {
628 Log.e(TAG, "Requested engine doesn't support TTS V2 API");
629 return null;
630 }
631
632 return new EngineStatus(mServiceConnection.getEngineName(), voices);
633 }
634
635 private class Connection implements ServiceConnection {
636 private final String mEngineName;
637
638 private ITextToSpeechService mService;
639
640 private boolean mEstablished;
641
642 private PrepareConnectionAsyncTask mSetupConnectionAsyncTask;
643
644 public Connection(String engineName) {
645 this.mEngineName = engineName;
646 }
647
648 private final ITextToSpeechCallback.Stub mCallback = new ITextToSpeechCallback.Stub() {
649
650 @Override
651 public void onStart(String utteranceIdStr) {
652 synchronized (mLock) {
653 Pair<UtteranceId, RequestCallbacks> callbacks = getCallback(utteranceIdStr);
654 callbacks.second.onSynthesisStart(callbacks.first);
655 }
656 }
657
658 public void onStop(String utteranceIdStr) {
659 synchronized (mLock) {
660 Pair<UtteranceId, RequestCallbacks> callbacks = removeCallback(utteranceIdStr);
661 callbacks.second.onSynthesisStop(callbacks.first);
662 }
663 }
664
665 @Override
666 public void onSuccess(String utteranceIdStr) {
667 synchronized (mLock) {
668 Pair<UtteranceId, RequestCallbacks> callbacks = removeCallback(utteranceIdStr);
669 callbacks.second.onSynthesisSuccess(callbacks.first);
670 }
671 }
672
673 public void onFallback(String utteranceIdStr) {
674 synchronized (mLock) {
Przemyslaw Szczepaniak1ca1d8862014-01-29 15:20:06 +0000675 Pair<UtteranceId, RequestCallbacks> callbacks = getCallback(
676 utteranceIdStr);
Przemyslaw Szczepaniak90d15d22013-06-14 12:02:53 +0100677 callbacks.second.onSynthesisFallback(callbacks.first);
678 }
679 };
680
681 @Override
682 public void onError(String utteranceIdStr, int errorCode) {
683 removeCallbackAndErr(utteranceIdStr, errorCode);
684 }
685
686 @Override
687 public void onVoicesInfoChange(List<VoiceInfo> voicesInfo) {
688 synchronized (mLock) {
689 mEngineStatus = new EngineStatus(mServiceConnection.getEngineName(),
690 voicesInfo);
691 mConnectionCallbacks.onEngineStatusChange(mEngineStatus);
692 }
693 }
694 };
695
696 private class PrepareConnectionAsyncTask extends AsyncTask<Void, Void, EngineStatus> {
697
698 private final ComponentName mName;
699
700 public PrepareConnectionAsyncTask(ComponentName name) {
701 mName = name;
702 }
703
704 @Override
705 protected EngineStatus doInBackground(Void... params) {
706 synchronized(mLock) {
707 if (isCancelled()) {
708 return null;
709 }
710 try {
711 mService.setCallback(getCallerIdentity(), mCallback);
712 return requestEngineStatus(mService);
713 } catch (RemoteException re) {
714 Log.e(TAG, "Error setting up the TTS service");
715 return null;
716 }
717 }
718 }
719
720 @Override
721 protected void onPostExecute(EngineStatus result) {
722 synchronized(mLock) {
723 if (mSetupConnectionAsyncTask == this) {
724 mSetupConnectionAsyncTask = null;
725 }
726 if (result == null) {
727 Log.e(TAG, "Setup task failed");
728 disconnect();
729 mConnectionCallbacks.onConnectionFailure();
730 return;
731 }
732
733 mEngineStatus = result;
734 mEstablished = true;
735 }
736 mConnectionCallbacks.onConnectionSuccess();
737 }
738 }
739
740 @Override
741 public void onServiceConnected(ComponentName name, IBinder service) {
742 Log.i(TAG, "Connected to " + name);
743
744 synchronized(mLock) {
745 mEstablished = false;
746 mService = ITextToSpeechService.Stub.asInterface(service);
747 startSetupConnectionTask(name);
748 }
749 }
750
751 @Override
752 public void onServiceDisconnected(ComponentName name) {
753 Log.i(TAG, "Asked to disconnect from " + name);
754
755 synchronized(mLock) {
756 stopSetupConnectionTask();
757 }
758 mConnectionCallbacks.onServiceDisconnected();
759 }
760
761 private void startSetupConnectionTask(ComponentName name) {
762 stopSetupConnectionTask();
763 mSetupConnectionAsyncTask = new PrepareConnectionAsyncTask(name);
764 mSetupConnectionAsyncTask.execute();
765 }
766
767 private boolean stopSetupConnectionTask() {
768 boolean result = false;
769 if (mSetupConnectionAsyncTask != null) {
770 result = mSetupConnectionAsyncTask.cancel(false);
771 mSetupConnectionAsyncTask = null;
772 }
773 return result;
774 }
775
776 IBinder getCallerIdentity() {
777 return mCallback;
778 }
779
780 boolean isEstablished() {
781 return mService != null && mEstablished;
782 }
783
784 boolean runAction(Action action) {
785 synchronized (mLock) {
786 try {
787 action.run(mService);
788 return true;
789 } catch (Exception ex) {
790 Log.e(TAG, action.getName() + " failed", ex);
791 disconnect();
792 return false;
793 }
794 }
795 }
796
797 void disconnect() {
798 mContext.unbindService(this);
799 stopSetupConnectionTask();
800 mService = null;
801 mEstablished = false;
802 if (mServiceConnection == this) {
803 mServiceConnection = null;
804 }
805 }
806
807 String getEngineName() {
808 return mEngineName;
809 }
810 }
811
812 private abstract class Action {
813 private final String mName;
814
815 public Action(String name) {
816 mName = name;
817 }
818
819 public String getName() {return mName;}
820 abstract void run(ITextToSpeechService service) throws RemoteException;
821 }
822
823 private IBinder getCallerIdentity() {
824 if (mServiceConnection != null) {
825 return mServiceConnection.getCallerIdentity();
826 }
827 return null;
828 }
829
830 private boolean runAction(Action action) {
831 synchronized (mLock) {
832 if (mServiceConnection == null) {
833 return false;
834 }
835 if (!mServiceConnection.isEstablished()) {
836 return false;
837 }
838 mServiceConnection.runAction(action);
839 return true;
840 }
841 }
842
843 private static final String ACTION_STOP_NAME = "stop";
844
845 /**
846 * Interrupts the current utterance spoken (whether played or rendered to file) and discards
847 * other utterances in the queue.
848 */
849 public void stop() {
850 runAction(new Action(ACTION_STOP_NAME) {
851 @Override
852 public void run(ITextToSpeechService service) throws RemoteException {
853 if (service.stop(getCallerIdentity()) != Status.SUCCESS) {
854 Log.e(TAG, "Stop failed");
855 }
856 mCallbacks.clear();
857 }
858 });
859 }
860
861 private static final String ACTION_QUEUE_SPEAK_NAME = "queueSpeak";
862
863 /**
864 * Speaks the string using the specified queuing strategy using current
865 * voice. This method is asynchronous, i.e. the method just adds the request
866 * to the queue of TTS requests and then returns. The synthesis might not
867 * have finished (or even started!) at the time when this method returns.
868 *
869 * @param utterance The string of text to be spoken. No longer than
870 * 1000 characters.
871 * @param utteranceId Unique identificator used to track the synthesis progress
872 * in {@link RequestCallbacks}.
873 * @param config Synthesis request configuration. Can't be null. Has to contain a
874 * voice.
875 * @param callbacks Synthesis request callbacks. If null, default request
876 * callbacks object will be used.
877 */
878 public void queueSpeak(final String utterance, final UtteranceId utteranceId,
879 final RequestConfig config,
880 final RequestCallbacks callbacks) {
881 runAction(new Action(ACTION_QUEUE_SPEAK_NAME) {
882 @Override
883 public void run(ITextToSpeechService service) throws RemoteException {
884 RequestCallbacks c = mDefaultRequestCallbacks;
885 if (callbacks != null) {
886 c = callbacks;
887 }
888 int addCallbackStatus = addCallback(utteranceId, c);
889 if (addCallbackStatus != Status.SUCCESS) {
890 c.onSynthesisFailure(utteranceId, Status.ERROR_INVALID_REQUEST);
891 return;
892 }
893
894 int queueResult = service.speakV2(
895 getCallerIdentity(),
896 new SynthesisRequestV2(utterance, utteranceId.toUniqueString(), config));
897 if (queueResult != Status.SUCCESS) {
898 removeCallbackAndErr(utteranceId.toUniqueString(), queueResult);
899 }
900 }
901 });
902 }
903
904 private static final String ACTION_QUEUE_SYNTHESIZE_TO_FILE = "queueSynthesizeToFile";
905
906 /**
907 * Synthesizes the given text to a file using the specified parameters. This
908 * method is asynchronous, i.e. the method just adds the request to the
909 * queue of TTS requests and then returns. The synthesis might not have
910 * finished (or even started!) at the time when this method returns.
911 *
912 * @param utterance The text that should be synthesized. No longer than
913 * 1000 characters.
914 * @param utteranceId Unique identificator used to track the synthesis progress
915 * in {@link RequestCallbacks}.
916 * @param outputFile File to write the generated audio data to.
917 * @param config Synthesis request configuration. Can't be null. Have to contain a
918 * voice.
919 * @param callbacks Synthesis request callbacks. If null, default request
920 * callbacks object will be used.
921 */
922 public void queueSynthesizeToFile(final String utterance, final UtteranceId utteranceId,
923 final File outputFile, final RequestConfig config,
924 final RequestCallbacks callbacks) {
925 runAction(new Action(ACTION_QUEUE_SYNTHESIZE_TO_FILE) {
926 @Override
927 public void run(ITextToSpeechService service) throws RemoteException {
928 RequestCallbacks c = mDefaultRequestCallbacks;
929 if (callbacks != null) {
930 c = callbacks;
931 }
932 int addCallbackStatus = addCallback(utteranceId, c);
933 if (addCallbackStatus != Status.SUCCESS) {
934 c.onSynthesisFailure(utteranceId, Status.ERROR_INVALID_REQUEST);
935 return;
936 }
937
938 ParcelFileDescriptor fileDescriptor = null;
939 try {
940 if (outputFile.exists() && !outputFile.canWrite()) {
941 Log.e(TAG, "No permissions to write to " + outputFile);
942 removeCallbackAndErr(utteranceId.toUniqueString(), Status.ERROR_OUTPUT);
943 return;
944 }
945 fileDescriptor = ParcelFileDescriptor.open(outputFile,
946 ParcelFileDescriptor.MODE_WRITE_ONLY |
947 ParcelFileDescriptor.MODE_CREATE |
948 ParcelFileDescriptor.MODE_TRUNCATE);
949
950 int queueResult = service.synthesizeToFileDescriptorV2(getCallerIdentity(),
951 fileDescriptor,
952 new SynthesisRequestV2(utterance, utteranceId.toUniqueString(),
953 config));
954 fileDescriptor.close();
955 if (queueResult != Status.SUCCESS) {
956 removeCallbackAndErr(utteranceId.toUniqueString(), queueResult);
957 }
958 } catch (FileNotFoundException e) {
959 Log.e(TAG, "Opening file " + outputFile + " failed", e);
960 removeCallbackAndErr(utteranceId.toUniqueString(), Status.ERROR_OUTPUT);
961 } catch (IOException e) {
962 Log.e(TAG, "Closing file " + outputFile + " failed", e);
963 removeCallbackAndErr(utteranceId.toUniqueString(), Status.ERROR_OUTPUT);
964 }
965 }
966 });
967 }
968
969 private static final String ACTION_QUEUE_SILENCE_NAME = "queueSilence";
970
971 /**
972 * Plays silence for the specified amount of time. This method is asynchronous,
973 * i.e. the method just adds the request to the queue of TTS requests and then
974 * returns. The synthesis might not have finished (or even started!) at the time
975 * when this method returns.
976 *
977 * @param durationInMs The duration of the silence in milliseconds.
978 * @param utteranceId Unique identificator used to track the synthesis progress
979 * in {@link RequestCallbacks}.
980 * @param callbacks Synthesis request callbacks. If null, default request
981 * callbacks object will be used.
982 */
983 public void queueSilence(final long durationInMs, final UtteranceId utteranceId,
984 final RequestCallbacks callbacks) {
985 runAction(new Action(ACTION_QUEUE_SILENCE_NAME) {
986 @Override
987 public void run(ITextToSpeechService service) throws RemoteException {
988 RequestCallbacks c = mDefaultRequestCallbacks;
989 if (callbacks != null) {
990 c = callbacks;
991 }
992 int addCallbackStatus = addCallback(utteranceId, c);
993 if (addCallbackStatus != Status.SUCCESS) {
994 c.onSynthesisFailure(utteranceId, Status.ERROR_INVALID_REQUEST);
995 }
996
997 int queueResult = service.playSilence(getCallerIdentity(), durationInMs,
998 TextToSpeech.QUEUE_ADD, utteranceId.toUniqueString());
999
1000 if (queueResult != Status.SUCCESS) {
1001 removeCallbackAndErr(utteranceId.toUniqueString(), queueResult);
1002 }
1003 }
1004 });
1005 }
1006
1007
1008 private static final String ACTION_QUEUE_AUDIO_NAME = "queueAudio";
1009
1010 /**
1011 * Plays the audio resource using the specified parameters.
1012 * This method is asynchronous, i.e. the method just adds the request to the queue of TTS
1013 * requests and then returns. The synthesis might not have finished (or even started!) at the
1014 * time when this method returns.
1015 *
1016 * @param audioUrl The audio resource that should be played
1017 * @param utteranceId Unique identificator used to track synthesis progress
1018 * in {@link RequestCallbacks}.
1019 * @param config Synthesis request configuration. Can't be null. Doesn't have to contain a
1020 * voice (only system parameters are used).
1021 * @param callbacks Synthesis request callbacks. If null, default request
1022 * callbacks object will be used.
1023 */
1024 public void queueAudio(final Uri audioUrl, final UtteranceId utteranceId,
1025 final RequestConfig config, final RequestCallbacks callbacks) {
1026 runAction(new Action(ACTION_QUEUE_AUDIO_NAME) {
1027 @Override
1028 public void run(ITextToSpeechService service) throws RemoteException {
1029 RequestCallbacks c = mDefaultRequestCallbacks;
1030 if (callbacks != null) {
1031 c = callbacks;
1032 }
1033 int addCallbackStatus = addCallback(utteranceId, c);
1034 if (addCallbackStatus != Status.SUCCESS) {
1035 c.onSynthesisFailure(utteranceId, Status.ERROR_INVALID_REQUEST);
1036 }
1037
1038 int queueResult = service.playAudioV2(getCallerIdentity(), audioUrl,
1039 utteranceId.toUniqueString(), config.getVoiceParams());
1040
1041 if (queueResult != Status.SUCCESS) {
1042 removeCallbackAndErr(utteranceId.toUniqueString(), queueResult);
1043 }
1044 }
1045 });
1046 }
1047}