blob: c562327d15b6ab067fefc5a8a42f2fa1811b5c82 [file] [log] [blame]
Charles Chena042a742009-05-26 20:55:10 -07001/*
2 * Copyright (C) 2009 Google Inc.
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.tts;
17
Charles Chena042a742009-05-26 20:55:10 -070018import android.app.Service;
Jean-Michel Trivi605a66b2009-06-17 16:36:46 -070019import android.content.ContentResolver;
Charles Chena042a742009-05-26 20:55:10 -070020import android.content.Context;
21import android.content.Intent;
22import android.content.SharedPreferences;
Charles Chen998207c2009-10-20 11:07:14 -070023import android.content.pm.ActivityInfo;
Charles Chena042a742009-05-26 20:55:10 -070024import android.content.pm.PackageManager;
Charles Chena042a742009-05-26 20:55:10 -070025import android.content.pm.PackageManager.NameNotFoundException;
Charles Chen998207c2009-10-20 11:07:14 -070026import android.content.pm.ResolveInfo;
Charles Chen726a40b2010-03-17 23:47:07 -070027import android.database.Cursor;
Jean-Michel Trivi9440bce2009-07-13 10:12:37 -070028import android.media.AudioManager;
Charles Chena042a742009-05-26 20:55:10 -070029import android.media.MediaPlayer;
30import android.media.MediaPlayer.OnCompletionListener;
31import android.net.Uri;
32import android.os.IBinder;
33import android.os.RemoteCallbackList;
34import android.os.RemoteException;
35import android.preference.PreferenceManager;
Jean-Michel Trivid1468742009-06-18 14:41:41 -070036import android.speech.tts.ITts.Stub;
37import android.speech.tts.ITtsCallback;
38import android.speech.tts.TextToSpeech;
Charles Chena042a742009-05-26 20:55:10 -070039import android.util.Log;
Jean-Michel Trivia3046b62009-08-26 16:36:24 -070040
41import java.io.File;
Jean-Michel Trivib21651c12010-03-04 12:12:56 -080042import java.io.IOException;
Charles Chena042a742009-05-26 20:55:10 -070043import java.util.ArrayList;
44import java.util.Arrays;
45import java.util.HashMap;
Charles Chen998207c2009-10-20 11:07:14 -070046import java.util.List;
Jean-Michel Trivid478cf02009-07-06 17:58:52 -070047import java.util.Locale;
Charles Chena042a742009-05-26 20:55:10 -070048import java.util.concurrent.locks.ReentrantLock;
Charles Chen0a3d8742009-07-08 19:02:19 -070049import java.util.concurrent.TimeUnit;
50
Charles Chena042a742009-05-26 20:55:10 -070051
52/**
53 * @hide Synthesizes speech from text. This is implemented as a service so that
54 * other applications can call the TTS without needing to bundle the TTS
55 * in the build.
56 *
57 */
58public class TtsService extends Service implements OnCompletionListener {
59
Jean-Michel Trivi78ebbab2009-06-02 15:09:51 -070060 private static class SpeechItem {
Charles Chen741c25b2009-06-25 17:11:29 -070061 public static final int TEXT = 0;
Charles Chen4bca97ec2009-07-02 17:21:22 -070062 public static final int EARCON = 1;
63 public static final int SILENCE = 2;
64 public static final int TEXT_TO_FILE = 3;
Charles Chenb02ced72009-07-07 14:33:52 -070065 public String mText = "";
Jean-Michel Trivi78ebbab2009-06-02 15:09:51 -070066 public ArrayList<String> mParams = null;
Charles Chen741c25b2009-06-25 17:11:29 -070067 public int mType = TEXT;
Jean-Michel Trivi78ebbab2009-06-02 15:09:51 -070068 public long mDuration = 0;
Charles Chenebb814b2009-06-30 12:01:12 -070069 public String mFilename = null;
Charles Chen78c9d0d2009-07-13 16:22:41 -070070 public String mCallingApp = "";
Charles Chena042a742009-05-26 20:55:10 -070071
Charles Chena9c5e4b2009-07-09 20:27:15 -070072 public SpeechItem(String source, String text, ArrayList<String> params, int itemType) {
Jean-Michel Trivi78ebbab2009-06-02 15:09:51 -070073 mText = text;
74 mParams = params;
75 mType = itemType;
Charles Chen78c9d0d2009-07-13 16:22:41 -070076 mCallingApp = source;
Charles Chena042a742009-05-26 20:55:10 -070077 }
Charles Chena042a742009-05-26 20:55:10 -070078
Charles Chen3ab20762009-07-13 21:21:01 -070079 public SpeechItem(String source, long silenceTime, ArrayList<String> params) {
Jean-Michel Trivi78ebbab2009-06-02 15:09:51 -070080 mDuration = silenceTime;
Charles Chen3ab20762009-07-13 21:21:01 -070081 mParams = params;
Charles Chenb02ced72009-07-07 14:33:52 -070082 mType = SILENCE;
Charles Chen78c9d0d2009-07-13 16:22:41 -070083 mCallingApp = source;
Charles Chena042a742009-05-26 20:55:10 -070084 }
Charles Chenebb814b2009-06-30 12:01:12 -070085
Charles Chenedb4fc32009-07-14 10:32:31 -070086 public SpeechItem(String source, String text, ArrayList<String> params,
87 int itemType, String filename) {
Charles Chenebb814b2009-06-30 12:01:12 -070088 mText = text;
89 mParams = params;
90 mType = itemType;
91 mFilename = filename;
Charles Chen78c9d0d2009-07-13 16:22:41 -070092 mCallingApp = source;
Charles Chenebb814b2009-06-30 12:01:12 -070093 }
94
Charles Chena042a742009-05-26 20:55:10 -070095 }
Charles Chena042a742009-05-26 20:55:10 -070096
Jean-Michel Trivi78ebbab2009-06-02 15:09:51 -070097 /**
98 * Contains the information needed to access a sound resource; the name of
99 * the package that contains the resource and the resID of the resource
100 * within that package.
101 */
102 private static class SoundResource {
103 public String mSourcePackageName = null;
104 public int mResId = -1;
105 public String mFilename = null;
Charles Chena042a742009-05-26 20:55:10 -0700106
Jean-Michel Trivi78ebbab2009-06-02 15:09:51 -0700107 public SoundResource(String packageName, int id) {
108 mSourcePackageName = packageName;
109 mResId = id;
110 mFilename = null;
Charles Chena042a742009-05-26 20:55:10 -0700111 }
Jean-Michel Trivi78ebbab2009-06-02 15:09:51 -0700112
113 public SoundResource(String file) {
114 mSourcePackageName = null;
115 mResId = -1;
116 mFilename = file;
117 }
118 }
Charles Chen0dbc6a42009-07-27 13:43:08 -0700119 // If the speech queue is locked for more than 5 seconds, something has gone
120 // very wrong with processSpeechQueue.
121 private static final int SPEECHQUEUELOCK_TIMEOUT = 5000;
Charles Chen1aacdcf2009-06-25 15:58:59 -0700122 private static final int MAX_SPEECH_ITEM_CHAR_LENGTH = 4000;
Charles Chen03454f82009-06-29 14:04:24 -0700123 private static final int MAX_FILENAME_LENGTH = 250;
Jean-Michel Trivi9440bce2009-07-13 10:12:37 -0700124 private static final int DEFAULT_STREAM_TYPE = AudioManager.STREAM_MUSIC;
Charles Chendd5b4e22010-03-17 19:15:41 -0700125 // TODO use TextToSpeech.DEFAULT_SYNTH once it is unhidden
126 private static final String DEFAULT_SYNTH = "com.svox.pico";
Charles Chen52ae0652009-07-06 16:10:32 -0700127 private static final String ACTION = "android.intent.action.START_TTS_SERVICE";
Jean-Michel Trivi78ebbab2009-06-02 15:09:51 -0700128 private static final String CATEGORY = "android.intent.category.TTS";
129 private static final String PKGNAME = "android.tts";
Jean-Michel Trivib0094182009-08-31 14:46:18 -0700130 protected static final String SERVICE_TAG = "TtsService";
Jean-Michel Trivi78ebbab2009-06-02 15:09:51 -0700131
Charles Chenedb4fc32009-07-14 10:32:31 -0700132 private final RemoteCallbackList<ITtsCallback> mCallbacks
133 = new RemoteCallbackList<ITtsCallback>();
134
135 private HashMap<String, ITtsCallback> mCallbacksMap;
Jean-Michel Trivi78ebbab2009-06-02 15:09:51 -0700136
137 private Boolean mIsSpeaking;
Charles Chen44afb7b2010-02-05 21:36:07 -0800138 private Boolean mSynthBusy;
Jean-Michel Trivi78ebbab2009-06-02 15:09:51 -0700139 private ArrayList<SpeechItem> mSpeechQueue;
140 private HashMap<String, SoundResource> mEarcons;
141 private HashMap<String, SoundResource> mUtterances;
142 private MediaPlayer mPlayer;
Charles Chen3ab20762009-07-13 21:21:01 -0700143 private SpeechItem mCurrentSpeechItem;
Charles Chenc231fd02009-07-16 11:28:09 -0700144 private HashMap<SpeechItem, Boolean> mKillList; // Used to ensure that in-flight synth calls
145 // are killed when stop is used.
Jean-Michel Trivi78ebbab2009-06-02 15:09:51 -0700146 private TtsService mSelf;
147
Jean-Michel Trivi605a66b2009-06-17 16:36:46 -0700148 private ContentResolver mResolver;
Jean-Michel Trivi78ebbab2009-06-02 15:09:51 -0700149
Jean-Michel Trivia3046b62009-08-26 16:36:24 -0700150 // lock for the speech queue (mSpeechQueue) and the current speech item (mCurrentSpeechItem)
Jean-Michel Trivi78ebbab2009-06-02 15:09:51 -0700151 private final ReentrantLock speechQueueLock = new ReentrantLock();
152 private final ReentrantLock synthesizerLock = new ReentrantLock();
153
Jean-Michel Trivia73d5cd2009-07-21 10:13:41 -0700154 private static SynthProxy sNativeSynth = null;
Charles Chen998207c2009-10-20 11:07:14 -0700155 private String currentSpeechEngineSOFile = "";
156
Jean-Michel Trivi78ebbab2009-06-02 15:09:51 -0700157 @Override
158 public void onCreate() {
159 super.onCreate();
Jean-Michel Triviab6ed2c2009-08-28 09:25:11 -0700160 Log.v("TtsService", "TtsService.onCreate()");
Jean-Michel Trivi78ebbab2009-06-02 15:09:51 -0700161
Jean-Michel Trivi605a66b2009-06-17 16:36:46 -0700162 mResolver = getContentResolver();
Jean-Michel Trivi78ebbab2009-06-02 15:09:51 -0700163
Charles Chen998207c2009-10-20 11:07:14 -0700164 currentSpeechEngineSOFile = "";
165 setEngine(getDefaultEngine());
166
Jean-Michel Trivi78ebbab2009-06-02 15:09:51 -0700167 mSelf = this;
168 mIsSpeaking = false;
Charles Chen44afb7b2010-02-05 21:36:07 -0800169 mSynthBusy = false;
Jean-Michel Trivi78ebbab2009-06-02 15:09:51 -0700170
171 mEarcons = new HashMap<String, SoundResource>();
172 mUtterances = new HashMap<String, SoundResource>();
Charles Chen78c9d0d2009-07-13 16:22:41 -0700173 mCallbacksMap = new HashMap<String, android.speech.tts.ITtsCallback>();
Jean-Michel Trivi78ebbab2009-06-02 15:09:51 -0700174
175 mSpeechQueue = new ArrayList<SpeechItem>();
176 mPlayer = null;
Charles Chen3ab20762009-07-13 21:21:01 -0700177 mCurrentSpeechItem = null;
Charles Chenc231fd02009-07-16 11:28:09 -0700178 mKillList = new HashMap<SpeechItem, Boolean>();
Jean-Michel Trivi78ebbab2009-06-02 15:09:51 -0700179
Jean-Michel Trivi605a66b2009-06-17 16:36:46 -0700180 setDefaultSettings();
Jean-Michel Trivi78ebbab2009-06-02 15:09:51 -0700181 }
182
183 @Override
184 public void onDestroy() {
185 super.onDestroy();
Jean-Michel Trivi69e67a32009-08-10 16:13:18 -0700186
Jean-Michel Trivia3046b62009-08-26 16:36:24 -0700187 killAllUtterances();
Jean-Michel Trivi69e67a32009-08-10 16:13:18 -0700188
Jean-Michel Trivi78ebbab2009-06-02 15:09:51 -0700189 // Don't hog the media player
Charles Chena042a742009-05-26 20:55:10 -0700190 cleanUpPlayer();
Jean-Michel Trivi78ebbab2009-06-02 15:09:51 -0700191
Jean-Michel Trivi69e67a32009-08-10 16:13:18 -0700192 if (sNativeSynth != null) {
193 sNativeSynth.shutdown();
194 }
Jean-Michel Trivicee3bd42009-07-21 14:12:47 -0700195 sNativeSynth = null;
Jean-Michel Trivi78ebbab2009-06-02 15:09:51 -0700196
197 // Unregister all callbacks.
198 mCallbacks.kill();
Jean-Michel Trivi09f8db72009-08-31 10:41:55 -0700199
Jean-Michel Trivib0094182009-08-31 14:46:18 -0700200 Log.v(SERVICE_TAG, "onDestroy() completed");
Jean-Michel Trivi78ebbab2009-06-02 15:09:51 -0700201 }
202
Jean-Michel Trivi605a66b2009-06-17 16:36:46 -0700203
Charles Chen998207c2009-10-20 11:07:14 -0700204 private int setEngine(String enginePackageName) {
205 String soFilename = "";
Charles Chen6a8b73b2010-03-01 18:52:41 -0800206 if (isDefaultEnforced()) {
207 enginePackageName = getDefaultEngine();
208 }
Charles Chendd5b4e22010-03-17 19:15:41 -0700209
210 // Make sure that the engine has been allowed by the user
211 if (!enginePackageName.equals(DEFAULT_SYNTH)) {
212 String[] enabledEngines = android.provider.Settings.Secure.getString(mResolver,
213 android.provider.Settings.Secure.TTS_ENABLED_PLUGINS).split(" ");
214 boolean isEnabled = false;
215 for (int i=0; i<enabledEngines.length; i++) {
216 if (enabledEngines[i].equals(enginePackageName)) {
217 isEnabled = true;
218 break;
219 }
220 }
221 if (!isEnabled) {
222 // Do not use an engine that the user has not enabled; fall back
223 // to using the default synthesizer.
224 enginePackageName = DEFAULT_SYNTH;
225 }
226 }
227
Charles Chen998207c2009-10-20 11:07:14 -0700228 // The SVOX TTS is an exception to how the TTS packaging scheme works
229 // because it is part of the system and not a 3rd party add-on; thus
230 // its binary is actually located under /system/lib/
Charles Chendd5b4e22010-03-17 19:15:41 -0700231 if (enginePackageName.equals(DEFAULT_SYNTH)) {
Charles Chen998207c2009-10-20 11:07:14 -0700232 soFilename = "/system/lib/libttspico.so";
233 } else {
234 // Find the package
235 Intent intent = new Intent("android.intent.action.START_TTS_ENGINE");
236 intent.setPackage(enginePackageName);
237 ResolveInfo[] enginesArray = new ResolveInfo[0];
238 PackageManager pm = getPackageManager();
239 List <ResolveInfo> resolveInfos = pm.queryIntentActivities(intent, 0);
240 if ((resolveInfos == null) || resolveInfos.isEmpty()) {
241 Log.e(SERVICE_TAG, "Invalid TTS Engine Package: " + enginePackageName);
242 return TextToSpeech.ERROR;
243 }
244 enginesArray = resolveInfos.toArray(enginesArray);
245 // Generate the TTS .so filename from the package
246 ActivityInfo aInfo = enginesArray[0].activityInfo;
247 soFilename = aInfo.name.replace(aInfo.packageName + ".", "") + ".so";
248 soFilename = soFilename.toLowerCase();
249 soFilename = "/data/data/" + aInfo.packageName + "/lib/libtts" + soFilename;
250 }
251
252 if (currentSpeechEngineSOFile.equals(soFilename)) {
253 return TextToSpeech.SUCCESS;
254 }
255
256 File f = new File(soFilename);
257 if (!f.exists()) {
258 Log.e(SERVICE_TAG, "Invalid TTS Binary: " + soFilename);
259 return TextToSpeech.ERROR;
260 }
261
262 if (sNativeSynth != null) {
263 sNativeSynth.stopSync();
264 sNativeSynth.shutdown();
265 sNativeSynth = null;
266 }
Charles Chen726a40b2010-03-17 23:47:07 -0700267
268 // Load the engineConfig from the plugin if it has any special configuration
269 // to be loaded. By convention, if an engine wants the TTS framework to pass
270 // in any configuration, it must put it into its content provider which has the URI:
271 // content://<packageName>.providers.SettingsProvider
272 // That content provider must provide a Cursor which returns the String that
273 // is to be passed back to the native .so file for the plugin when getString(0) is
274 // called on it.
275 // Note that the TTS framework does not care what this String data is: it is something
276 // that comes from the engine plugin and is consumed only by the engine plugin itself.
277 String engineConfig = "";
278 Cursor c = getContentResolver().query(Uri.parse("content://" + enginePackageName
279 + ".providers.SettingsProvider"), null, null, null, null);
280 if (c != null){
281 c.moveToFirst();
282 engineConfig = c.getString(0);
283 c.close();
284 }
285 sNativeSynth = new SynthProxy(soFilename, engineConfig);
Charles Chen998207c2009-10-20 11:07:14 -0700286 currentSpeechEngineSOFile = soFilename;
287 return TextToSpeech.SUCCESS;
288 }
289
290
291
Jean-Michel Trivi605a66b2009-06-17 16:36:46 -0700292 private void setDefaultSettings() {
Charles Chena9c5e4b2009-07-09 20:27:15 -0700293 setLanguage("", this.getDefaultLanguage(), getDefaultCountry(), getDefaultLocVariant());
Jean-Michel Trivi605a66b2009-06-17 16:36:46 -0700294
295 // speech rate
Charles Chena9c5e4b2009-07-09 20:27:15 -0700296 setSpeechRate("", getDefaultRate());
Jean-Michel Trivi605a66b2009-06-17 16:36:46 -0700297 }
298
299
300 private boolean isDefaultEnforced() {
301 return (android.provider.Settings.Secure.getInt(mResolver,
Jean-Michel Trivid1468742009-06-18 14:41:41 -0700302 android.provider.Settings.Secure.TTS_USE_DEFAULTS,
Jean-Michel Trivied065782009-07-28 14:31:48 -0700303 TextToSpeech.Engine.USE_DEFAULTS)
Jean-Michel Trivi605a66b2009-06-17 16:36:46 -0700304 == 1 );
305 }
306
Charles Chen998207c2009-10-20 11:07:14 -0700307 private String getDefaultEngine() {
308 String defaultEngine = android.provider.Settings.Secure.getString(mResolver,
309 android.provider.Settings.Secure.TTS_DEFAULT_SYNTH);
310 if (defaultEngine == null) {
311 return TextToSpeech.Engine.DEFAULT_SYNTH;
312 } else {
313 return defaultEngine;
314 }
315 }
Jean-Michel Trivi605a66b2009-06-17 16:36:46 -0700316
317 private int getDefaultRate() {
318 return android.provider.Settings.Secure.getInt(mResolver,
Jean-Michel Trivid1468742009-06-18 14:41:41 -0700319 android.provider.Settings.Secure.TTS_DEFAULT_RATE,
Jean-Michel Trivied065782009-07-28 14:31:48 -0700320 TextToSpeech.Engine.DEFAULT_RATE);
Jean-Michel Trivi605a66b2009-06-17 16:36:46 -0700321 }
322
Charles Chen1a2712c2010-04-01 17:16:28 -0700323 private int getDefaultPitch() {
324 // Pitch is not user settable; the default pitch is always 100.
325 return 100;
326 }
Jean-Michel Trivi605a66b2009-06-17 16:36:46 -0700327
Jean-Michel Trivi6a0e2932009-06-24 11:32:06 -0700328 private String getDefaultLanguage() {
329 String defaultLang = android.provider.Settings.Secure.getString(mResolver,
330 android.provider.Settings.Secure.TTS_DEFAULT_LANG);
331 if (defaultLang == null) {
Jean-Michel Trivid478cf02009-07-06 17:58:52 -0700332 // no setting found, use the current Locale to determine the default language
333 return Locale.getDefault().getISO3Language();
Jean-Michel Trivi6a0e2932009-06-24 11:32:06 -0700334 } else {
335 return defaultLang;
336 }
337 }
338
339
340 private String getDefaultCountry() {
341 String defaultCountry = android.provider.Settings.Secure.getString(mResolver,
342 android.provider.Settings.Secure.TTS_DEFAULT_COUNTRY);
343 if (defaultCountry == null) {
Jean-Michel Trivid478cf02009-07-06 17:58:52 -0700344 // no setting found, use the current Locale to determine the default country
345 return Locale.getDefault().getISO3Country();
Jean-Michel Trivi6a0e2932009-06-24 11:32:06 -0700346 } else {
347 return defaultCountry;
348 }
349 }
350
351
352 private String getDefaultLocVariant() {
353 String defaultVar = android.provider.Settings.Secure.getString(mResolver,
354 android.provider.Settings.Secure.TTS_DEFAULT_VARIANT);
355 if (defaultVar == null) {
Jean-Michel Trivid478cf02009-07-06 17:58:52 -0700356 // no setting found, use the current Locale to determine the default variant
357 return Locale.getDefault().getVariant();
Jean-Michel Trivi6a0e2932009-06-24 11:32:06 -0700358 } else {
359 return defaultVar;
360 }
361 }
362
363
Charles Chena9c5e4b2009-07-09 20:27:15 -0700364 private int setSpeechRate(String callingApp, int rate) {
Jean-Michel Trivi69e67a32009-08-10 16:13:18 -0700365 int res = TextToSpeech.ERROR;
366 try {
367 if (isDefaultEnforced()) {
368 res = sNativeSynth.setSpeechRate(getDefaultRate());
369 } else {
370 res = sNativeSynth.setSpeechRate(rate);
371 }
372 } catch (NullPointerException e) {
373 // synth will become null during onDestroy()
374 res = TextToSpeech.ERROR;
Charles Chena042a742009-05-26 20:55:10 -0700375 }
Jean-Michel Trivi69e67a32009-08-10 16:13:18 -0700376 return res;
Jean-Michel Trivi78ebbab2009-06-02 15:09:51 -0700377 }
Charles Chena042a742009-05-26 20:55:10 -0700378
Jean-Michel Trivi2ea53492009-06-23 13:44:40 -0700379
Charles Chena9c5e4b2009-07-09 20:27:15 -0700380 private int setPitch(String callingApp, int pitch) {
Jean-Michel Trivi69e67a32009-08-10 16:13:18 -0700381 int res = TextToSpeech.ERROR;
382 try {
383 res = sNativeSynth.setPitch(pitch);
384 } catch (NullPointerException e) {
385 // synth will become null during onDestroy()
386 res = TextToSpeech.ERROR;
387 }
388 return res;
Jean-Michel Trivi2ea53492009-06-23 13:44:40 -0700389 }
390
391
Jean-Michel Trividdb0a802009-06-29 16:38:47 -0700392 private int isLanguageAvailable(String lang, String country, String variant) {
Jean-Michel Trivi69e67a32009-08-10 16:13:18 -0700393 int res = TextToSpeech.LANG_NOT_SUPPORTED;
394 try {
395 res = sNativeSynth.isLanguageAvailable(lang, country, variant);
396 } catch (NullPointerException e) {
397 // synth will become null during onDestroy()
398 res = TextToSpeech.LANG_NOT_SUPPORTED;
399 }
400 return res;
Jean-Michel Trividdb0a802009-06-29 16:38:47 -0700401 }
402
403
404 private String[] getLanguage() {
Jean-Michel Trivi69e67a32009-08-10 16:13:18 -0700405 try {
406 return sNativeSynth.getLanguage();
407 } catch (Exception e) {
408 return null;
409 }
Jean-Michel Trividdb0a802009-06-29 16:38:47 -0700410 }
411
412
Charles Chena9c5e4b2009-07-09 20:27:15 -0700413 private int setLanguage(String callingApp, String lang, String country, String variant) {
Jean-Michel Trivib0094182009-08-31 14:46:18 -0700414 Log.v(SERVICE_TAG, "TtsService.setLanguage(" + lang + ", " + country + ", " + variant + ")");
Jean-Michel Trivi69e67a32009-08-10 16:13:18 -0700415 int res = TextToSpeech.ERROR;
416 try {
417 if (isDefaultEnforced()) {
418 res = sNativeSynth.setLanguage(getDefaultLanguage(), getDefaultCountry(),
419 getDefaultLocVariant());
420 } else {
421 res = sNativeSynth.setLanguage(lang, country, variant);
422 }
423 } catch (NullPointerException e) {
424 // synth will become null during onDestroy()
425 res = TextToSpeech.ERROR;
Jean-Michel Trivi605a66b2009-06-17 16:36:46 -0700426 }
Jean-Michel Trivi69e67a32009-08-10 16:13:18 -0700427 return res;
Jean-Michel Trivi78ebbab2009-06-02 15:09:51 -0700428 }
429
Jean-Michel Trivi6a0e2932009-06-24 11:32:06 -0700430
Charles Chena042a742009-05-26 20:55:10 -0700431 /**
Jean-Michel Trivi78ebbab2009-06-02 15:09:51 -0700432 * Adds a sound resource to the TTS.
Charles Chena042a742009-05-26 20:55:10 -0700433 *
Jean-Michel Trivi78ebbab2009-06-02 15:09:51 -0700434 * @param text
435 * The text that should be associated with the sound resource
436 * @param packageName
437 * The name of the package which has the sound resource
438 * @param resId
439 * The resource ID of the sound within its package
Charles Chena042a742009-05-26 20:55:10 -0700440 */
Charles Chena9c5e4b2009-07-09 20:27:15 -0700441 private void addSpeech(String callingApp, String text, String packageName, int resId) {
Jean-Michel Trivi78ebbab2009-06-02 15:09:51 -0700442 mUtterances.put(text, new SoundResource(packageName, resId));
Charles Chena042a742009-05-26 20:55:10 -0700443 }
444
445 /**
Jean-Michel Trivi78ebbab2009-06-02 15:09:51 -0700446 * Adds a sound resource to the TTS.
Charles Chena042a742009-05-26 20:55:10 -0700447 *
Jean-Michel Trivi78ebbab2009-06-02 15:09:51 -0700448 * @param text
449 * The text that should be associated with the sound resource
450 * @param filename
451 * The filename of the sound resource. This must be a complete
452 * path like: (/sdcard/mysounds/mysoundbite.mp3).
Charles Chena042a742009-05-26 20:55:10 -0700453 */
Charles Chena9c5e4b2009-07-09 20:27:15 -0700454 private void addSpeech(String callingApp, String text, String filename) {
Jean-Michel Trivi78ebbab2009-06-02 15:09:51 -0700455 mUtterances.put(text, new SoundResource(filename));
Charles Chena042a742009-05-26 20:55:10 -0700456 }
457
458 /**
Jean-Michel Trivi78ebbab2009-06-02 15:09:51 -0700459 * Adds a sound resource to the TTS as an earcon.
460 *
461 * @param earcon
462 * The text that should be associated with the sound resource
463 * @param packageName
464 * The name of the package which has the sound resource
465 * @param resId
466 * The resource ID of the sound within its package
467 */
Charles Chena9c5e4b2009-07-09 20:27:15 -0700468 private void addEarcon(String callingApp, String earcon, String packageName, int resId) {
Jean-Michel Trivi78ebbab2009-06-02 15:09:51 -0700469 mEarcons.put(earcon, new SoundResource(packageName, resId));
470 }
471
472 /**
473 * Adds a sound resource to the TTS as an earcon.
474 *
475 * @param earcon
476 * The text that should be associated with the sound resource
477 * @param filename
478 * The filename of the sound resource. This must be a complete
479 * path like: (/sdcard/mysounds/mysoundbite.mp3).
480 */
Charles Chena9c5e4b2009-07-09 20:27:15 -0700481 private void addEarcon(String callingApp, String earcon, String filename) {
Jean-Michel Trivi78ebbab2009-06-02 15:09:51 -0700482 mEarcons.put(earcon, new SoundResource(filename));
483 }
484
485 /**
486 * Speaks the given text using the specified queueing mode and parameters.
Charles Chena042a742009-05-26 20:55:10 -0700487 *
488 * @param text
489 * The text that should be spoken
490 * @param queueMode
Jean-Michel Trivid48ca222009-07-10 10:04:37 -0700491 * TextToSpeech.TTS_QUEUE_FLUSH for no queue (interrupts all previous utterances),
492 * TextToSpeech.TTS_QUEUE_ADD for queued
Charles Chena042a742009-05-26 20:55:10 -0700493 * @param params
Jean-Michel Trivi78ebbab2009-06-02 15:09:51 -0700494 * An ArrayList of parameters. This is not implemented for all
495 * engines.
Charles Chena042a742009-05-26 20:55:10 -0700496 */
Charles Chena9c5e4b2009-07-09 20:27:15 -0700497 private int speak(String callingApp, String text, int queueMode, ArrayList<String> params) {
Charles Chena19ac0f2010-11-19 10:34:47 -0800498 // Log.v(SERVICE_TAG, "TTS service received " + text);
Jean-Michel Trivied065782009-07-28 14:31:48 -0700499 if (queueMode == TextToSpeech.QUEUE_FLUSH) {
Charles Chena9c5e4b2009-07-09 20:27:15 -0700500 stop(callingApp);
Charles Chen6f624232009-07-14 19:26:24 -0700501 } else if (queueMode == 2) {
502 stopAll(callingApp);
Jean-Michel Trivi78ebbab2009-06-02 15:09:51 -0700503 }
Charles Chena9c5e4b2009-07-09 20:27:15 -0700504 mSpeechQueue.add(new SpeechItem(callingApp, text, params, SpeechItem.TEXT));
Charles Chen741c25b2009-06-25 17:11:29 -0700505 if (!mIsSpeaking) {
506 processSpeechQueue();
507 }
Jean-Michel Trivied065782009-07-28 14:31:48 -0700508 return TextToSpeech.SUCCESS;
Charles Chen741c25b2009-06-25 17:11:29 -0700509 }
510
511 /**
Charles Chena042a742009-05-26 20:55:10 -0700512 * Plays the earcon using the specified queueing mode and parameters.
513 *
514 * @param earcon
515 * The earcon that should be played
516 * @param queueMode
Jean-Michel Trivid48ca222009-07-10 10:04:37 -0700517 * TextToSpeech.TTS_QUEUE_FLUSH for no queue (interrupts all previous utterances),
518 * TextToSpeech.TTS_QUEUE_ADD for queued
Charles Chena042a742009-05-26 20:55:10 -0700519 * @param params
Jean-Michel Trivi78ebbab2009-06-02 15:09:51 -0700520 * An ArrayList of parameters. This is not implemented for all
521 * engines.
Charles Chena042a742009-05-26 20:55:10 -0700522 */
Charles Chena9c5e4b2009-07-09 20:27:15 -0700523 private int playEarcon(String callingApp, String earcon, int queueMode,
Jean-Michel Trivi78ebbab2009-06-02 15:09:51 -0700524 ArrayList<String> params) {
Jean-Michel Trivied065782009-07-28 14:31:48 -0700525 if (queueMode == TextToSpeech.QUEUE_FLUSH) {
Charles Chena9c5e4b2009-07-09 20:27:15 -0700526 stop(callingApp);
Charles Chen6f624232009-07-14 19:26:24 -0700527 } else if (queueMode == 2) {
528 stopAll(callingApp);
Jean-Michel Trivi78ebbab2009-06-02 15:09:51 -0700529 }
Charles Chena9c5e4b2009-07-09 20:27:15 -0700530 mSpeechQueue.add(new SpeechItem(callingApp, earcon, params, SpeechItem.EARCON));
Jean-Michel Trivi78ebbab2009-06-02 15:09:51 -0700531 if (!mIsSpeaking) {
532 processSpeechQueue();
533 }
Jean-Michel Trivied065782009-07-28 14:31:48 -0700534 return TextToSpeech.SUCCESS;
Charles Chena042a742009-05-26 20:55:10 -0700535 }
536
537 /**
Charles Chen6f624232009-07-14 19:26:24 -0700538 * Stops all speech output and removes any utterances still in the queue for the calling app.
Charles Chena042a742009-05-26 20:55:10 -0700539 */
Charles Chena9c5e4b2009-07-09 20:27:15 -0700540 private int stop(String callingApp) {
Jean-Michel Trivied065782009-07-28 14:31:48 -0700541 int result = TextToSpeech.ERROR;
Charles Chen0a3d8742009-07-08 19:02:19 -0700542 boolean speechQueueAvailable = false;
543 try{
Charles Chen0dbc6a42009-07-27 13:43:08 -0700544 speechQueueAvailable =
545 speechQueueLock.tryLock(SPEECHQUEUELOCK_TIMEOUT, TimeUnit.MILLISECONDS);
Charles Chen0a3d8742009-07-08 19:02:19 -0700546 if (speechQueueAvailable) {
Jean-Michel Trivib0094182009-08-31 14:46:18 -0700547 Log.i(SERVICE_TAG, "Stopping");
Charles Chena9c5e4b2009-07-09 20:27:15 -0700548 for (int i = mSpeechQueue.size() - 1; i > -1; i--){
Charles Chen78c9d0d2009-07-13 16:22:41 -0700549 if (mSpeechQueue.get(i).mCallingApp.equals(callingApp)){
Charles Chena9c5e4b2009-07-09 20:27:15 -0700550 mSpeechQueue.remove(i);
551 }
552 }
Charles Chen6f624232009-07-14 19:26:24 -0700553 if ((mCurrentSpeechItem != null) &&
554 mCurrentSpeechItem.mCallingApp.equals(callingApp)) {
Jean-Michel Trivi69e67a32009-08-10 16:13:18 -0700555 try {
556 result = sNativeSynth.stop();
557 } catch (NullPointerException e1) {
558 // synth will become null during onDestroy()
559 result = TextToSpeech.ERROR;
560 }
Charles Chenc231fd02009-07-16 11:28:09 -0700561 mKillList.put(mCurrentSpeechItem, true);
Charles Chen6f624232009-07-14 19:26:24 -0700562 if (mPlayer != null) {
563 try {
564 mPlayer.stop();
565 } catch (IllegalStateException e) {
566 // Do nothing, the player is already stopped.
567 }
Charles Chen0a3d8742009-07-08 19:02:19 -0700568 }
Charles Chen6f624232009-07-14 19:26:24 -0700569 mIsSpeaking = false;
570 mCurrentSpeechItem = null;
571 } else {
Jean-Michel Trivied065782009-07-28 14:31:48 -0700572 result = TextToSpeech.SUCCESS;
Charles Chen0a3d8742009-07-08 19:02:19 -0700573 }
Jean-Michel Trivib0094182009-08-31 14:46:18 -0700574 Log.i(SERVICE_TAG, "Stopped");
Jean-Michel Triviab6ed2c2009-08-28 09:25:11 -0700575 } else {
Jean-Michel Trivib0094182009-08-31 14:46:18 -0700576 Log.e(SERVICE_TAG, "TTS stop(): queue locked longer than expected");
Jean-Michel Triviab6ed2c2009-08-28 09:25:11 -0700577 result = TextToSpeech.ERROR;
Jean-Michel Trivi78ebbab2009-06-02 15:09:51 -0700578 }
Charles Chen0a3d8742009-07-08 19:02:19 -0700579 } catch (InterruptedException e) {
Jean-Michel Trivib0094182009-08-31 14:46:18 -0700580 Log.e(SERVICE_TAG, "TTS stop: tryLock interrupted");
Charles Chen0a3d8742009-07-08 19:02:19 -0700581 e.printStackTrace();
582 } finally {
583 // This check is needed because finally will always run; even if the
584 // method returns somewhere in the try block.
Charles Chen6f624232009-07-14 19:26:24 -0700585 if (speechQueueAvailable) {
586 speechQueueLock.unlock();
587 }
588 return result;
589 }
590 }
591
592
Jean-Michel Trivia3046b62009-08-26 16:36:24 -0700593 /**
594 * Stops all speech output, both rendered to a file and directly spoken, and removes any
595 * utterances still in the queue globally. Files that were being written are deleted.
596 */
597 @SuppressWarnings("finally")
598 private int killAllUtterances() {
599 int result = TextToSpeech.ERROR;
600 boolean speechQueueAvailable = false;
601
602 try {
603 speechQueueAvailable = speechQueueLock.tryLock(SPEECHQUEUELOCK_TIMEOUT,
604 TimeUnit.MILLISECONDS);
605 if (speechQueueAvailable) {
606 // remove every single entry in the speech queue
607 mSpeechQueue.clear();
608
609 // clear the current speech item
610 if (mCurrentSpeechItem != null) {
Jean-Michel Trivi09f8db72009-08-31 10:41:55 -0700611 result = sNativeSynth.stopSync();
Jean-Michel Trivia3046b62009-08-26 16:36:24 -0700612 mKillList.put(mCurrentSpeechItem, true);
613 mIsSpeaking = false;
614
615 // was the engine writing to a file?
616 if (mCurrentSpeechItem.mType == SpeechItem.TEXT_TO_FILE) {
617 // delete the file that was being written
Jean-Michel Trivia3046b62009-08-26 16:36:24 -0700618 if (mCurrentSpeechItem.mFilename != null) {
619 File tempFile = new File(mCurrentSpeechItem.mFilename);
Jean-Michel Trivib0094182009-08-31 14:46:18 -0700620 Log.v(SERVICE_TAG, "Leaving behind " + mCurrentSpeechItem.mFilename);
Jean-Michel Trivia3046b62009-08-26 16:36:24 -0700621 if (tempFile.exists()) {
Jean-Michel Trivib0094182009-08-31 14:46:18 -0700622 Log.v(SERVICE_TAG, "About to delete "
Jean-Michel Trivia3046b62009-08-26 16:36:24 -0700623 + mCurrentSpeechItem.mFilename);
624 if (tempFile.delete()) {
Jean-Michel Trivib0094182009-08-31 14:46:18 -0700625 Log.v(SERVICE_TAG, "file successfully deleted");
Jean-Michel Trivia3046b62009-08-26 16:36:24 -0700626 }
627 }
628 }
629 }
630
631 mCurrentSpeechItem = null;
632 }
633 } else {
Jean-Michel Trivib0094182009-08-31 14:46:18 -0700634 Log.e(SERVICE_TAG, "TTS killAllUtterances(): queue locked longer than expected");
Jean-Michel Trivia3046b62009-08-26 16:36:24 -0700635 result = TextToSpeech.ERROR;
636 }
637 } catch (InterruptedException e) {
Jean-Michel Trivib0094182009-08-31 14:46:18 -0700638 Log.e(SERVICE_TAG, "TTS killAllUtterances(): tryLock interrupted");
Jean-Michel Trivia3046b62009-08-26 16:36:24 -0700639 result = TextToSpeech.ERROR;
640 } finally {
641 // This check is needed because finally will always run, even if the
642 // method returns somewhere in the try block.
643 if (speechQueueAvailable) {
644 speechQueueLock.unlock();
645 }
646 return result;
647 }
648 }
649
Charles Chen6f624232009-07-14 19:26:24 -0700650
651 /**
Jean-Michel Trivi69e67a32009-08-10 16:13:18 -0700652 * Stops all speech output and removes any utterances still in the queue globally, except
653 * those intended to be synthesized to file.
Charles Chen6f624232009-07-14 19:26:24 -0700654 */
655 private int stopAll(String callingApp) {
Jean-Michel Trivied065782009-07-28 14:31:48 -0700656 int result = TextToSpeech.ERROR;
Charles Chen6f624232009-07-14 19:26:24 -0700657 boolean speechQueueAvailable = false;
658 try{
Charles Chen0dbc6a42009-07-27 13:43:08 -0700659 speechQueueAvailable =
660 speechQueueLock.tryLock(SPEECHQUEUELOCK_TIMEOUT, TimeUnit.MILLISECONDS);
Charles Chen6f624232009-07-14 19:26:24 -0700661 if (speechQueueAvailable) {
662 for (int i = mSpeechQueue.size() - 1; i > -1; i--){
663 if (mSpeechQueue.get(i).mType != SpeechItem.TEXT_TO_FILE){
664 mSpeechQueue.remove(i);
665 }
666 }
667 if ((mCurrentSpeechItem != null) &&
668 ((mCurrentSpeechItem.mType != SpeechItem.TEXT_TO_FILE) ||
669 mCurrentSpeechItem.mCallingApp.equals(callingApp))) {
Jean-Michel Trivi69e67a32009-08-10 16:13:18 -0700670 try {
671 result = sNativeSynth.stop();
672 } catch (NullPointerException e1) {
673 // synth will become null during onDestroy()
674 result = TextToSpeech.ERROR;
675 }
Charles Chenc231fd02009-07-16 11:28:09 -0700676 mKillList.put(mCurrentSpeechItem, true);
Charles Chen6f624232009-07-14 19:26:24 -0700677 if (mPlayer != null) {
678 try {
679 mPlayer.stop();
680 } catch (IllegalStateException e) {
681 // Do nothing, the player is already stopped.
682 }
683 }
684 mIsSpeaking = false;
685 mCurrentSpeechItem = null;
686 } else {
Jean-Michel Trivied065782009-07-28 14:31:48 -0700687 result = TextToSpeech.SUCCESS;
Charles Chen6f624232009-07-14 19:26:24 -0700688 }
Jean-Michel Trivib0094182009-08-31 14:46:18 -0700689 Log.i(SERVICE_TAG, "Stopped all");
Jean-Michel Triviab6ed2c2009-08-28 09:25:11 -0700690 } else {
Jean-Michel Trivib0094182009-08-31 14:46:18 -0700691 Log.e(SERVICE_TAG, "TTS stopAll(): queue locked longer than expected");
Jean-Michel Triviab6ed2c2009-08-28 09:25:11 -0700692 result = TextToSpeech.ERROR;
Charles Chen6f624232009-07-14 19:26:24 -0700693 }
694 } catch (InterruptedException e) {
Jean-Michel Trivib0094182009-08-31 14:46:18 -0700695 Log.e(SERVICE_TAG, "TTS stopAll: tryLock interrupted");
Charles Chen6f624232009-07-14 19:26:24 -0700696 e.printStackTrace();
697 } finally {
698 // This check is needed because finally will always run; even if the
699 // method returns somewhere in the try block.
Charles Chen0a3d8742009-07-08 19:02:19 -0700700 if (speechQueueAvailable) {
701 speechQueueLock.unlock();
702 }
703 return result;
Jean-Michel Trivi78ebbab2009-06-02 15:09:51 -0700704 }
Charles Chena042a742009-05-26 20:55:10 -0700705 }
706
Charles Chena19ac0f2010-11-19 10:34:47 -0800707 public void onCompletion(MediaPlayer arg0) {
708 // mCurrentSpeechItem may become null if it is stopped at the same
709 // time it completes.
710 SpeechItem currentSpeechItemCopy = mCurrentSpeechItem;
711 if (currentSpeechItemCopy != null) {
712 String callingApp = currentSpeechItemCopy.mCallingApp;
713 ArrayList<String> params = currentSpeechItemCopy.mParams;
714 String utteranceId = "";
715 if (params != null) {
716 for (int i = 0; i < params.size() - 1; i = i + 2) {
717 String param = params.get(i);
718 if (param.equals(TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID)) {
719 utteranceId = params.get(i + 1);
720 }
721 }
722 }
723 if (utteranceId.length() > 0) {
724 dispatchUtteranceCompletedCallback(utteranceId, callingApp);
725 }
726 }
727 processSpeechQueue();
Jean-Michel Trivi78ebbab2009-06-02 15:09:51 -0700728 }
Charles Chena042a742009-05-26 20:55:10 -0700729
Charles Chena9c5e4b2009-07-09 20:27:15 -0700730 private int playSilence(String callingApp, long duration, int queueMode,
Jean-Michel Trivi78ebbab2009-06-02 15:09:51 -0700731 ArrayList<String> params) {
Jean-Michel Trivied065782009-07-28 14:31:48 -0700732 if (queueMode == TextToSpeech.QUEUE_FLUSH) {
Charles Chena9c5e4b2009-07-09 20:27:15 -0700733 stop(callingApp);
Jean-Michel Trivi78ebbab2009-06-02 15:09:51 -0700734 }
Charles Chen3ab20762009-07-13 21:21:01 -0700735 mSpeechQueue.add(new SpeechItem(callingApp, duration, params));
Jean-Michel Trivi78ebbab2009-06-02 15:09:51 -0700736 if (!mIsSpeaking) {
737 processSpeechQueue();
738 }
Jean-Michel Trivied065782009-07-28 14:31:48 -0700739 return TextToSpeech.SUCCESS;
Jean-Michel Trivi78ebbab2009-06-02 15:09:51 -0700740 }
741
Charles Chen3ab20762009-07-13 21:21:01 -0700742 private void silence(final SpeechItem speechItem) {
Jean-Michel Trivi78ebbab2009-06-02 15:09:51 -0700743 class SilenceThread implements Runnable {
744 public void run() {
Charles Chen3ab20762009-07-13 21:21:01 -0700745 String utteranceId = "";
Charles Chenedb4fc32009-07-14 10:32:31 -0700746 if (speechItem.mParams != null){
747 for (int i = 0; i < speechItem.mParams.size() - 1; i = i + 2){
748 String param = speechItem.mParams.get(i);
Jean-Michel Trivied065782009-07-28 14:31:48 -0700749 if (param.equals(TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID)){
Charles Chenedb4fc32009-07-14 10:32:31 -0700750 utteranceId = speechItem.mParams.get(i+1);
Charles Chen3ab20762009-07-13 21:21:01 -0700751 }
752 }
753 }
Jean-Michel Trivi78ebbab2009-06-02 15:09:51 -0700754 try {
Charles Chenedb4fc32009-07-14 10:32:31 -0700755 Thread.sleep(speechItem.mDuration);
Jean-Michel Trivi78ebbab2009-06-02 15:09:51 -0700756 } catch (InterruptedException e) {
757 e.printStackTrace();
758 } finally {
Charles Chen3ab20762009-07-13 21:21:01 -0700759 if (utteranceId.length() > 0){
Charles Chenedb4fc32009-07-14 10:32:31 -0700760 dispatchUtteranceCompletedCallback(utteranceId, speechItem.mCallingApp);
Charles Chen3ab20762009-07-13 21:21:01 -0700761 }
Jean-Michel Trivi78ebbab2009-06-02 15:09:51 -0700762 processSpeechQueue();
763 }
764 }
765 }
766 Thread slnc = (new Thread(new SilenceThread()));
767 slnc.setPriority(Thread.MIN_PRIORITY);
768 slnc.start();
769 }
770
Charles Chen78c9d0d2009-07-13 16:22:41 -0700771 private void speakInternalOnly(final SpeechItem speechItem) {
Jean-Michel Trivi78ebbab2009-06-02 15:09:51 -0700772 class SynthThread implements Runnable {
773 public void run() {
774 boolean synthAvailable = false;
Charles Chen78c9d0d2009-07-13 16:22:41 -0700775 String utteranceId = "";
Jean-Michel Trivi78ebbab2009-06-02 15:09:51 -0700776 try {
777 synthAvailable = synthesizerLock.tryLock();
778 if (!synthAvailable) {
Charles Chen44afb7b2010-02-05 21:36:07 -0800779 mSynthBusy = true;
Jean-Michel Trivi78ebbab2009-06-02 15:09:51 -0700780 Thread.sleep(100);
781 Thread synth = (new Thread(new SynthThread()));
Jean-Michel Trivi78ebbab2009-06-02 15:09:51 -0700782 synth.start();
Charles Chen44afb7b2010-02-05 21:36:07 -0800783 mSynthBusy = false;
Jean-Michel Trivi78ebbab2009-06-02 15:09:51 -0700784 return;
785 }
Jean-Michel Trivi9440bce2009-07-13 10:12:37 -0700786 int streamType = DEFAULT_STREAM_TYPE;
Charles Chenb79370a2009-07-17 20:57:21 -0700787 String language = "";
788 String country = "";
789 String variant = "";
790 String speechRate = "";
Charles Chen60dd3602010-01-07 18:56:24 -0800791 String engine = "";
Charles Chen1a2712c2010-04-01 17:16:28 -0700792 String pitch = "";
Jean-Michel Trivi9d2d26a2011-01-05 16:08:21 -0800793 float volume = TextToSpeech.Engine.DEFAULT_VOLUME;
794 float pan = TextToSpeech.Engine.DEFAULT_PAN;
Charles Chenedb4fc32009-07-14 10:32:31 -0700795 if (speechItem.mParams != null){
Charles Chenedb4fc32009-07-14 10:32:31 -0700796 for (int i = 0; i < speechItem.mParams.size() - 1; i = i + 2){
797 String param = speechItem.mParams.get(i);
Jean-Michel Trivi9440bce2009-07-13 10:12:37 -0700798 if (param != null) {
Jean-Michel Trivied065782009-07-28 14:31:48 -0700799 if (param.equals(TextToSpeech.Engine.KEY_PARAM_RATE)) {
Charles Chenb79370a2009-07-17 20:57:21 -0700800 speechRate = speechItem.mParams.get(i+1);
Jean-Michel Trivied065782009-07-28 14:31:48 -0700801 } else if (param.equals(TextToSpeech.Engine.KEY_PARAM_LANGUAGE)){
Charles Chenedb4fc32009-07-14 10:32:31 -0700802 language = speechItem.mParams.get(i+1);
Jean-Michel Trivied065782009-07-28 14:31:48 -0700803 } else if (param.equals(TextToSpeech.Engine.KEY_PARAM_COUNTRY)){
Charles Chenedb4fc32009-07-14 10:32:31 -0700804 country = speechItem.mParams.get(i+1);
Jean-Michel Trivied065782009-07-28 14:31:48 -0700805 } else if (param.equals(TextToSpeech.Engine.KEY_PARAM_VARIANT)){
Charles Chenedb4fc32009-07-14 10:32:31 -0700806 variant = speechItem.mParams.get(i+1);
Jean-Michel Trivied065782009-07-28 14:31:48 -0700807 } else if (param.equals(TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID)){
Charles Chenedb4fc32009-07-14 10:32:31 -0700808 utteranceId = speechItem.mParams.get(i+1);
Jean-Michel Trivied065782009-07-28 14:31:48 -0700809 } else if (param.equals(TextToSpeech.Engine.KEY_PARAM_STREAM)) {
Jean-Michel Trivi9440bce2009-07-13 10:12:37 -0700810 try {
Charles Chenedb4fc32009-07-14 10:32:31 -0700811 streamType
812 = Integer.parseInt(speechItem.mParams.get(i + 1));
Jean-Michel Trivi9440bce2009-07-13 10:12:37 -0700813 } catch (NumberFormatException e) {
814 streamType = DEFAULT_STREAM_TYPE;
815 }
Charles Chen60dd3602010-01-07 18:56:24 -0800816 } else if (param.equals(TextToSpeech.Engine.KEY_PARAM_ENGINE)) {
817 engine = speechItem.mParams.get(i + 1);
Charles Chen1a2712c2010-04-01 17:16:28 -0700818 } else if (param.equals(TextToSpeech.Engine.KEY_PARAM_PITCH)) {
819 pitch = speechItem.mParams.get(i + 1);
Jean-Michel Trivi9d2d26a2011-01-05 16:08:21 -0800820 } else if (param.equals(TextToSpeech.Engine.KEY_PARAM_VOLUME)) {
821 try {
822 volume = Float.parseFloat(speechItem.mParams.get(i + 1));
823 } catch (NumberFormatException e) {
824 volume = TextToSpeech.Engine.DEFAULT_VOLUME;
825 }
826 } else if (param.equals(TextToSpeech.Engine.KEY_PARAM_PAN)) {
827 try {
828 pan = Float.parseFloat(speechItem.mParams.get(i + 1));
829 } catch (NumberFormatException e) {
830 pan = TextToSpeech.Engine.DEFAULT_PAN;
831 }
Charles Chen630a8de2009-07-10 20:58:15 -0700832 }
Charles Chen03454f82009-06-29 14:04:24 -0700833 }
834 }
Charles Chenb79370a2009-07-17 20:57:21 -0700835 }
836 // Only do the synthesis if it has not been killed by a subsequent utterance.
837 if (mKillList.get(speechItem) == null) {
Charles Chen60dd3602010-01-07 18:56:24 -0800838 if (engine.length() > 0) {
839 setEngine(engine);
Charles Chen6a8b73b2010-03-01 18:52:41 -0800840 } else {
841 setEngine(getDefaultEngine());
Charles Chen60dd3602010-01-07 18:56:24 -0800842 }
Charles Chen03454f82009-06-29 14:04:24 -0700843 if (language.length() > 0){
Charles Chena9c5e4b2009-07-09 20:27:15 -0700844 setLanguage("", language, country, variant);
Charles Chen6a8b73b2010-03-01 18:52:41 -0800845 } else {
846 setLanguage("", getDefaultLanguage(), getDefaultCountry(),
847 getDefaultLocVariant());
Charles Chen03454f82009-06-29 14:04:24 -0700848 }
Charles Chenb79370a2009-07-17 20:57:21 -0700849 if (speechRate.length() > 0){
850 setSpeechRate("", Integer.parseInt(speechRate));
Charles Chen6a8b73b2010-03-01 18:52:41 -0800851 } else {
852 setSpeechRate("", getDefaultRate());
Charles Chenb79370a2009-07-17 20:57:21 -0700853 }
Charles Chen1a2712c2010-04-01 17:16:28 -0700854 if (pitch.length() > 0){
855 setPitch("", Integer.parseInt(pitch));
856 } else {
857 setPitch("", getDefaultPitch());
858 }
Jean-Michel Trivi69e67a32009-08-10 16:13:18 -0700859 try {
Jean-Michel Trivi9d2d26a2011-01-05 16:08:21 -0800860 sNativeSynth.speak(speechItem.mText, streamType, volume, pan);
Jean-Michel Trivi69e67a32009-08-10 16:13:18 -0700861 } catch (NullPointerException e) {
862 // synth will become null during onDestroy()
Jean-Michel Trivib0094182009-08-31 14:46:18 -0700863 Log.v(SERVICE_TAG, " null synth, can't speak");
Jean-Michel Trivi69e67a32009-08-10 16:13:18 -0700864 }
Charles Chenc231fd02009-07-16 11:28:09 -0700865 }
Jean-Michel Trivi78ebbab2009-06-02 15:09:51 -0700866 } catch (InterruptedException e) {
Jean-Michel Trivib0094182009-08-31 14:46:18 -0700867 Log.e(SERVICE_TAG, "TTS speakInternalOnly(): tryLock interrupted");
Jean-Michel Trivi78ebbab2009-06-02 15:09:51 -0700868 e.printStackTrace();
869 } finally {
870 // This check is needed because finally will always run;
871 // even if the
872 // method returns somewhere in the try block.
Charles Chen78c9d0d2009-07-13 16:22:41 -0700873 if (utteranceId.length() > 0){
Charles Chenedb4fc32009-07-14 10:32:31 -0700874 dispatchUtteranceCompletedCallback(utteranceId, speechItem.mCallingApp);
Charles Chen78c9d0d2009-07-13 16:22:41 -0700875 }
Charles Chen6f624232009-07-14 19:26:24 -0700876 if (synthAvailable) {
877 synthesizerLock.unlock();
Charles Chenf5c87b32010-02-05 16:00:17 -0800878 processSpeechQueue();
Charles Chen6f624232009-07-14 19:26:24 -0700879 }
Jean-Michel Trivi78ebbab2009-06-02 15:09:51 -0700880 }
881 }
882 }
883 Thread synth = (new Thread(new SynthThread()));
Jean-Michel Triviab6ed2c2009-08-28 09:25:11 -0700884 synth.setPriority(Thread.MAX_PRIORITY);
Jean-Michel Trivi78ebbab2009-06-02 15:09:51 -0700885 synth.start();
886 }
887
Charles Chen3ab20762009-07-13 21:21:01 -0700888 private void synthToFileInternalOnly(final SpeechItem speechItem) {
Charles Chenebb814b2009-06-30 12:01:12 -0700889 class SynthThread implements Runnable {
890 public void run() {
Charles Chenebb814b2009-06-30 12:01:12 -0700891 boolean synthAvailable = false;
Charles Chen3ab20762009-07-13 21:21:01 -0700892 String utteranceId = "";
Jean-Michel Trivib0094182009-08-31 14:46:18 -0700893 Log.i(SERVICE_TAG, "Synthesizing to " + speechItem.mFilename);
Charles Chenebb814b2009-06-30 12:01:12 -0700894 try {
895 synthAvailable = synthesizerLock.tryLock();
896 if (!synthAvailable) {
Charles Chen9b7cb792010-02-10 19:46:48 -0800897 synchronized (this) {
898 mSynthBusy = true;
899 }
Charles Chenebb814b2009-06-30 12:01:12 -0700900 Thread.sleep(100);
901 Thread synth = (new Thread(new SynthThread()));
Charles Chenebb814b2009-06-30 12:01:12 -0700902 synth.start();
Charles Chen9b7cb792010-02-10 19:46:48 -0800903 synchronized (this) {
904 mSynthBusy = false;
905 }
Charles Chenebb814b2009-06-30 12:01:12 -0700906 return;
907 }
Charles Chenb79370a2009-07-17 20:57:21 -0700908 String language = "";
909 String country = "";
910 String variant = "";
911 String speechRate = "";
Charles Chen60dd3602010-01-07 18:56:24 -0800912 String engine = "";
Charles Chen1a2712c2010-04-01 17:16:28 -0700913 String pitch = "";
Charles Chenedb4fc32009-07-14 10:32:31 -0700914 if (speechItem.mParams != null){
Charles Chenedb4fc32009-07-14 10:32:31 -0700915 for (int i = 0; i < speechItem.mParams.size() - 1; i = i + 2){
916 String param = speechItem.mParams.get(i);
Charles Chenb79370a2009-07-17 20:57:21 -0700917 if (param != null) {
Jean-Michel Trivied065782009-07-28 14:31:48 -0700918 if (param.equals(TextToSpeech.Engine.KEY_PARAM_RATE)) {
Charles Chenb79370a2009-07-17 20:57:21 -0700919 speechRate = speechItem.mParams.get(i+1);
Jean-Michel Trivied065782009-07-28 14:31:48 -0700920 } else if (param.equals(TextToSpeech.Engine.KEY_PARAM_LANGUAGE)){
Charles Chenedb4fc32009-07-14 10:32:31 -0700921 language = speechItem.mParams.get(i+1);
Jean-Michel Trivied065782009-07-28 14:31:48 -0700922 } else if (param.equals(TextToSpeech.Engine.KEY_PARAM_COUNTRY)){
Charles Chenedb4fc32009-07-14 10:32:31 -0700923 country = speechItem.mParams.get(i+1);
Jean-Michel Trivied065782009-07-28 14:31:48 -0700924 } else if (param.equals(TextToSpeech.Engine.KEY_PARAM_VARIANT)){
Charles Chenedb4fc32009-07-14 10:32:31 -0700925 variant = speechItem.mParams.get(i+1);
Jean-Michel Trivied065782009-07-28 14:31:48 -0700926 } else if (param.equals(TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID)){
Charles Chenedb4fc32009-07-14 10:32:31 -0700927 utteranceId = speechItem.mParams.get(i+1);
Charles Chen60dd3602010-01-07 18:56:24 -0800928 } else if (param.equals(TextToSpeech.Engine.KEY_PARAM_ENGINE)) {
929 engine = speechItem.mParams.get(i + 1);
Charles Chen1a2712c2010-04-01 17:16:28 -0700930 } else if (param.equals(TextToSpeech.Engine.KEY_PARAM_PITCH)) {
931 pitch = speechItem.mParams.get(i + 1);
Charles Chen630a8de2009-07-10 20:58:15 -0700932 }
Charles Chenebb814b2009-06-30 12:01:12 -0700933 }
934 }
Charles Chenebb814b2009-06-30 12:01:12 -0700935 }
Charles Chenc231fd02009-07-16 11:28:09 -0700936 // Only do the synthesis if it has not been killed by a subsequent utterance.
937 if (mKillList.get(speechItem) == null){
Charles Chen60dd3602010-01-07 18:56:24 -0800938 if (engine.length() > 0) {
939 setEngine(engine);
Charles Chen6a8b73b2010-03-01 18:52:41 -0800940 } else {
941 setEngine(getDefaultEngine());
Charles Chen60dd3602010-01-07 18:56:24 -0800942 }
Charles Chenb79370a2009-07-17 20:57:21 -0700943 if (language.length() > 0){
944 setLanguage("", language, country, variant);
Charles Chen6a8b73b2010-03-01 18:52:41 -0800945 } else {
946 setLanguage("", getDefaultLanguage(), getDefaultCountry(),
947 getDefaultLocVariant());
Charles Chenb79370a2009-07-17 20:57:21 -0700948 }
949 if (speechRate.length() > 0){
950 setSpeechRate("", Integer.parseInt(speechRate));
Charles Chen6a8b73b2010-03-01 18:52:41 -0800951 } else {
952 setSpeechRate("", getDefaultRate());
Charles Chenb79370a2009-07-17 20:57:21 -0700953 }
Charles Chen1a2712c2010-04-01 17:16:28 -0700954 if (pitch.length() > 0){
955 setPitch("", Integer.parseInt(pitch));
956 } else {
957 setPitch("", getDefaultPitch());
958 }
Jean-Michel Trivi69e67a32009-08-10 16:13:18 -0700959 try {
960 sNativeSynth.synthesizeToFile(speechItem.mText, speechItem.mFilename);
961 } catch (NullPointerException e) {
962 // synth will become null during onDestroy()
Jean-Michel Trivib0094182009-08-31 14:46:18 -0700963 Log.v(SERVICE_TAG, " null synth, can't synthesize to file");
Jean-Michel Trivi69e67a32009-08-10 16:13:18 -0700964 }
Charles Chenc231fd02009-07-16 11:28:09 -0700965 }
Charles Chenebb814b2009-06-30 12:01:12 -0700966 } catch (InterruptedException e) {
Jean-Michel Trivib0094182009-08-31 14:46:18 -0700967 Log.e(SERVICE_TAG, "TTS synthToFileInternalOnly(): tryLock interrupted");
Charles Chenebb814b2009-06-30 12:01:12 -0700968 e.printStackTrace();
969 } finally {
970 // This check is needed because finally will always run;
971 // even if the
972 // method returns somewhere in the try block.
Charles Chen3ab20762009-07-13 21:21:01 -0700973 if (utteranceId.length() > 0){
Charles Chenedb4fc32009-07-14 10:32:31 -0700974 dispatchUtteranceCompletedCallback(utteranceId, speechItem.mCallingApp);
Charles Chen3ab20762009-07-13 21:21:01 -0700975 }
Charles Chen6f624232009-07-14 19:26:24 -0700976 if (synthAvailable) {
977 synthesizerLock.unlock();
Charles Chenf5c87b32010-02-05 16:00:17 -0800978 processSpeechQueue();
Charles Chen6f624232009-07-14 19:26:24 -0700979 }
Charles Chenebb814b2009-06-30 12:01:12 -0700980 }
981 }
982 }
983 Thread synth = (new Thread(new SynthThread()));
Jean-Michel Triviab6ed2c2009-08-28 09:25:11 -0700984 synth.setPriority(Thread.MAX_PRIORITY);
Charles Chenebb814b2009-06-30 12:01:12 -0700985 synth.start();
986 }
987
Jean-Michel Trivi78ebbab2009-06-02 15:09:51 -0700988 private SoundResource getSoundResource(SpeechItem speechItem) {
989 SoundResource sr = null;
990 String text = speechItem.mText;
991 if (speechItem.mType == SpeechItem.SILENCE) {
992 // Do nothing if this is just silence
993 } else if (speechItem.mType == SpeechItem.EARCON) {
994 sr = mEarcons.get(text);
995 } else {
996 sr = mUtterances.get(text);
997 }
998 return sr;
999 }
1000
Charles Chen28dbae72009-06-24 17:47:21 -07001001 private void broadcastTtsQueueProcessingCompleted(){
Jean-Michel Trivied065782009-07-28 14:31:48 -07001002 Intent i = new Intent(TextToSpeech.ACTION_TTS_QUEUE_PROCESSING_COMPLETED);
Charles Chen28dbae72009-06-24 17:47:21 -07001003 sendBroadcast(i);
1004 }
1005
Charles Chen78c9d0d2009-07-13 16:22:41 -07001006
1007 private void dispatchUtteranceCompletedCallback(String utteranceId, String packageName) {
1008 ITtsCallback cb = mCallbacksMap.get(packageName);
1009 if (cb == null){
1010 return;
1011 }
Jean-Michel Trivib0094182009-08-31 14:46:18 -07001012 Log.v(SERVICE_TAG, "TTS callback: dispatch started");
Jean-Michel Trivi78ebbab2009-06-02 15:09:51 -07001013 // Broadcast to all clients the new value.
1014 final int N = mCallbacks.beginBroadcast();
Charles Chen78c9d0d2009-07-13 16:22:41 -07001015 try {
1016 cb.utteranceCompleted(utteranceId);
1017 } catch (RemoteException e) {
1018 // The RemoteCallbackList will take care of removing
1019 // the dead object for us.
Jean-Michel Trivi78ebbab2009-06-02 15:09:51 -07001020 }
1021 mCallbacks.finishBroadcast();
Jean-Michel Trivib0094182009-08-31 14:46:18 -07001022 Log.v(SERVICE_TAG, "TTS callback: dispatch completed to " + N);
Jean-Michel Trivi78ebbab2009-06-02 15:09:51 -07001023 }
1024
Charles Chen1aacdcf2009-06-25 15:58:59 -07001025 private SpeechItem splitCurrentTextIfNeeded(SpeechItem currentSpeechItem){
1026 if (currentSpeechItem.mText.length() < MAX_SPEECH_ITEM_CHAR_LENGTH){
1027 return currentSpeechItem;
1028 } else {
Charles Chen78c9d0d2009-07-13 16:22:41 -07001029 String callingApp = currentSpeechItem.mCallingApp;
Charles Chen1aacdcf2009-06-25 15:58:59 -07001030 ArrayList<SpeechItem> splitItems = new ArrayList<SpeechItem>();
1031 int start = 0;
1032 int end = start + MAX_SPEECH_ITEM_CHAR_LENGTH - 1;
1033 String splitText;
1034 SpeechItem splitItem;
1035 while (end < currentSpeechItem.mText.length()){
1036 splitText = currentSpeechItem.mText.substring(start, end);
Charles Chena9c5e4b2009-07-09 20:27:15 -07001037 splitItem = new SpeechItem(callingApp, splitText, null, SpeechItem.TEXT);
Charles Chen1aacdcf2009-06-25 15:58:59 -07001038 splitItems.add(splitItem);
1039 start = end;
1040 end = start + MAX_SPEECH_ITEM_CHAR_LENGTH - 1;
1041 }
1042 splitText = currentSpeechItem.mText.substring(start);
Charles Chena9c5e4b2009-07-09 20:27:15 -07001043 splitItem = new SpeechItem(callingApp, splitText, null, SpeechItem.TEXT);
Charles Chen1aacdcf2009-06-25 15:58:59 -07001044 splitItems.add(splitItem);
1045 mSpeechQueue.remove(0);
1046 for (int i = splitItems.size() - 1; i >= 0; i--){
1047 mSpeechQueue.add(0, splitItems.get(i));
1048 }
1049 return mSpeechQueue.get(0);
1050 }
1051 }
1052
Jean-Michel Trivi78ebbab2009-06-02 15:09:51 -07001053 private void processSpeechQueue() {
1054 boolean speechQueueAvailable = false;
Charles Chen44afb7b2010-02-05 21:36:07 -08001055 synchronized (this) {
1056 if (mSynthBusy){
1057 // There is already a synth thread waiting to run.
1058 return;
1059 }
1060 }
Jean-Michel Trivi78ebbab2009-06-02 15:09:51 -07001061 try {
Charles Chen0dbc6a42009-07-27 13:43:08 -07001062 speechQueueAvailable =
1063 speechQueueLock.tryLock(SPEECHQUEUELOCK_TIMEOUT, TimeUnit.MILLISECONDS);
Jean-Michel Trivi78ebbab2009-06-02 15:09:51 -07001064 if (!speechQueueAvailable) {
Jean-Michel Trivib0094182009-08-31 14:46:18 -07001065 Log.e(SERVICE_TAG, "processSpeechQueue - Speech queue is unavailable.");
Jean-Michel Trivi78ebbab2009-06-02 15:09:51 -07001066 return;
1067 }
1068 if (mSpeechQueue.size() < 1) {
1069 mIsSpeaking = false;
Jean-Michel Trivi09f8db72009-08-31 10:41:55 -07001070 mKillList.clear();
Charles Chen28dbae72009-06-24 17:47:21 -07001071 broadcastTtsQueueProcessingCompleted();
Jean-Michel Trivi78ebbab2009-06-02 15:09:51 -07001072 return;
1073 }
1074
Charles Chen3ab20762009-07-13 21:21:01 -07001075 mCurrentSpeechItem = mSpeechQueue.get(0);
Jean-Michel Trivi78ebbab2009-06-02 15:09:51 -07001076 mIsSpeaking = true;
Charles Chen3ab20762009-07-13 21:21:01 -07001077 SoundResource sr = getSoundResource(mCurrentSpeechItem);
Jean-Michel Trivi78ebbab2009-06-02 15:09:51 -07001078 // Synth speech as needed - synthesizer should call
1079 // processSpeechQueue to continue running the queue
Charles Chena19ac0f2010-11-19 10:34:47 -08001080 // Log.v(SERVICE_TAG, "TTS processing: " + mCurrentSpeechItem.mText);
Jean-Michel Trivi78ebbab2009-06-02 15:09:51 -07001081 if (sr == null) {
Charles Chen3ab20762009-07-13 21:21:01 -07001082 if (mCurrentSpeechItem.mType == SpeechItem.TEXT) {
1083 mCurrentSpeechItem = splitCurrentTextIfNeeded(mCurrentSpeechItem);
1084 speakInternalOnly(mCurrentSpeechItem);
1085 } else if (mCurrentSpeechItem.mType == SpeechItem.TEXT_TO_FILE) {
1086 synthToFileInternalOnly(mCurrentSpeechItem);
Jean-Michel Trivi78ebbab2009-06-02 15:09:51 -07001087 } else {
1088 // This is either silence or an earcon that was missing
Charles Chen3ab20762009-07-13 21:21:01 -07001089 silence(mCurrentSpeechItem);
Jean-Michel Trivi78ebbab2009-06-02 15:09:51 -07001090 }
1091 } else {
1092 cleanUpPlayer();
1093 if (sr.mSourcePackageName == PKGNAME) {
1094 // Utterance is part of the TTS library
1095 mPlayer = MediaPlayer.create(this, sr.mResId);
1096 } else if (sr.mSourcePackageName != null) {
1097 // Utterance is part of the app calling the library
1098 Context ctx;
1099 try {
Jean-Michel Trivi9440bce2009-07-13 10:12:37 -07001100 ctx = this.createPackageContext(sr.mSourcePackageName, 0);
Jean-Michel Trivi78ebbab2009-06-02 15:09:51 -07001101 } catch (NameNotFoundException e) {
1102 e.printStackTrace();
1103 mSpeechQueue.remove(0); // Remove it from the queue and
1104 // move on
1105 mIsSpeaking = false;
1106 return;
1107 }
1108 mPlayer = MediaPlayer.create(ctx, sr.mResId);
1109 } else {
1110 // Utterance is coming from a file
1111 mPlayer = MediaPlayer.create(this, Uri.parse(sr.mFilename));
1112 }
1113
1114 // Check if Media Server is dead; if it is, clear the queue and
1115 // give up for now - hopefully, it will recover itself.
1116 if (mPlayer == null) {
1117 mSpeechQueue.clear();
1118 mIsSpeaking = false;
1119 return;
1120 }
1121 mPlayer.setOnCompletionListener(this);
1122 try {
Charles Chen3ab20762009-07-13 21:21:01 -07001123 mPlayer.setAudioStreamType(getStreamTypeFromParams(mCurrentSpeechItem.mParams));
Jean-Michel Trivi78ebbab2009-06-02 15:09:51 -07001124 mPlayer.start();
1125 } catch (IllegalStateException e) {
1126 mSpeechQueue.clear();
1127 mIsSpeaking = false;
1128 cleanUpPlayer();
1129 return;
1130 }
1131 }
1132 if (mSpeechQueue.size() > 0) {
1133 mSpeechQueue.remove(0);
1134 }
Charles Chen0dbc6a42009-07-27 13:43:08 -07001135 } catch (InterruptedException e) {
Jean-Michel Trivib0094182009-08-31 14:46:18 -07001136 Log.e(SERVICE_TAG, "TTS processSpeechQueue: tryLock interrupted");
Charles Chen0dbc6a42009-07-27 13:43:08 -07001137 e.printStackTrace();
Jean-Michel Trivi78ebbab2009-06-02 15:09:51 -07001138 } finally {
1139 // This check is needed because finally will always run; even if the
1140 // method returns somewhere in the try block.
1141 if (speechQueueAvailable) {
1142 speechQueueLock.unlock();
1143 }
1144 }
1145 }
1146
Jean-Michel Trivi9440bce2009-07-13 10:12:37 -07001147 private int getStreamTypeFromParams(ArrayList<String> paramList) {
1148 int streamType = DEFAULT_STREAM_TYPE;
1149 if (paramList == null) {
1150 return streamType;
1151 }
1152 for (int i = 0; i < paramList.size() - 1; i = i + 2) {
1153 String param = paramList.get(i);
Jean-Michel Trivied065782009-07-28 14:31:48 -07001154 if ((param != null) && (param.equals(TextToSpeech.Engine.KEY_PARAM_STREAM))) {
Jean-Michel Trivi9440bce2009-07-13 10:12:37 -07001155 try {
1156 streamType = Integer.parseInt(paramList.get(i + 1));
1157 } catch (NumberFormatException e) {
1158 streamType = DEFAULT_STREAM_TYPE;
1159 }
1160 }
1161 }
1162 return streamType;
1163 }
1164
Jean-Michel Trivi78ebbab2009-06-02 15:09:51 -07001165 private void cleanUpPlayer() {
1166 if (mPlayer != null) {
1167 mPlayer.release();
1168 mPlayer = null;
1169 }
Charles Chena042a742009-05-26 20:55:10 -07001170 }
1171
1172 /**
Charles Chen741c25b2009-06-25 17:11:29 -07001173 * Synthesizes the given text to a file using the specified parameters.
Charles Chena042a742009-05-26 20:55:10 -07001174 *
1175 * @param text
1176 * The String of text that should be synthesized
1177 * @param params
Jean-Michel Trivi78ebbab2009-06-02 15:09:51 -07001178 * An ArrayList of parameters. The first element of this array
1179 * controls the type of voice to use.
Charles Chena042a742009-05-26 20:55:10 -07001180 * @param filename
Jean-Michel Trivi78ebbab2009-06-02 15:09:51 -07001181 * The string that gives the full output filename; it should be
1182 * something like "/sdcard/myappsounds/mysound.wav".
Jean-Michel Trivib21651c12010-03-04 12:12:56 -08001183 * @return A boolean that indicates if the synthesis can be started
Charles Chena042a742009-05-26 20:55:10 -07001184 */
Charles Chena9c5e4b2009-07-09 20:27:15 -07001185 private boolean synthesizeToFile(String callingApp, String text, ArrayList<String> params,
Charles Chenebb814b2009-06-30 12:01:12 -07001186 String filename) {
1187 // Don't allow a filename that is too long
1188 if (filename.length() > MAX_FILENAME_LENGTH) {
1189 return false;
Jean-Michel Trivi78ebbab2009-06-02 15:09:51 -07001190 }
Charles Chenebb814b2009-06-30 12:01:12 -07001191 // Don't allow anything longer than the max text length; since this
1192 // is synthing to a file, don't even bother splitting it.
1193 if (text.length() >= MAX_SPEECH_ITEM_CHAR_LENGTH){
1194 return false;
Jean-Michel Trivi78ebbab2009-06-02 15:09:51 -07001195 }
Jean-Michel Trivib21651c12010-03-04 12:12:56 -08001196 // Check that the output file can be created
1197 try {
1198 File tempFile = new File(filename);
1199 if (tempFile.exists()) {
1200 Log.v("TtsService", "File " + filename + " exists, deleting.");
1201 tempFile.delete();
1202 }
1203 if (!tempFile.createNewFile()) {
1204 Log.e("TtsService", "Unable to synthesize to file: can't create " + filename);
1205 return false;
1206 }
1207 tempFile.delete();
1208 } catch (IOException e) {
1209 Log.e("TtsService", "Can't create " + filename + " due to exception " + e);
1210 return false;
1211 }
Charles Chena9c5e4b2009-07-09 20:27:15 -07001212 mSpeechQueue.add(new SpeechItem(callingApp, text, params, SpeechItem.TEXT_TO_FILE, filename));
Charles Chenebb814b2009-06-30 12:01:12 -07001213 if (!mIsSpeaking) {
1214 processSpeechQueue();
1215 }
Jean-Michel Trivi78ebbab2009-06-02 15:09:51 -07001216 return true;
Charles Chena042a742009-05-26 20:55:10 -07001217 }
Jean-Michel Trivi78ebbab2009-06-02 15:09:51 -07001218
1219 @Override
1220 public IBinder onBind(Intent intent) {
1221 if (ACTION.equals(intent.getAction())) {
1222 for (String category : intent.getCategories()) {
1223 if (category.equals(CATEGORY)) {
1224 return mBinder;
1225 }
1226 }
1227 }
1228 return null;
1229 }
1230
Charles Chenf85aa5a2009-06-10 10:39:55 -07001231 private final android.speech.tts.ITts.Stub mBinder = new Stub() {
Jean-Michel Trivi78ebbab2009-06-02 15:09:51 -07001232
Charles Chen78c9d0d2009-07-13 16:22:41 -07001233 public int registerCallback(String packageName, ITtsCallback cb) {
1234 if (cb != null) {
Jean-Michel Trivi78ebbab2009-06-02 15:09:51 -07001235 mCallbacks.register(cb);
Charles Chen78c9d0d2009-07-13 16:22:41 -07001236 mCallbacksMap.put(packageName, cb);
Jean-Michel Trivied065782009-07-28 14:31:48 -07001237 return TextToSpeech.SUCCESS;
Charles Chen78c9d0d2009-07-13 16:22:41 -07001238 }
Jean-Michel Trivied065782009-07-28 14:31:48 -07001239 return TextToSpeech.ERROR;
Jean-Michel Trivi78ebbab2009-06-02 15:09:51 -07001240 }
1241
Charles Chen78c9d0d2009-07-13 16:22:41 -07001242 public int unregisterCallback(String packageName, ITtsCallback cb) {
1243 if (cb != null) {
1244 mCallbacksMap.remove(packageName);
Jean-Michel Trivi78ebbab2009-06-02 15:09:51 -07001245 mCallbacks.unregister(cb);
Jean-Michel Trivied065782009-07-28 14:31:48 -07001246 return TextToSpeech.SUCCESS;
Charles Chen78c9d0d2009-07-13 16:22:41 -07001247 }
Jean-Michel Trivied065782009-07-28 14:31:48 -07001248 return TextToSpeech.ERROR;
Jean-Michel Trivi78ebbab2009-06-02 15:09:51 -07001249 }
1250
1251 /**
Jean-Michel Trivi78ebbab2009-06-02 15:09:51 -07001252 * Speaks the given text using the specified queueing mode and
1253 * parameters.
1254 *
1255 * @param text
1256 * The text that should be spoken
1257 * @param queueMode
Jean-Michel Trivid48ca222009-07-10 10:04:37 -07001258 * TextToSpeech.TTS_QUEUE_FLUSH for no queue (interrupts all previous utterances)
1259 * TextToSpeech.TTS_QUEUE_ADD for queued
Jean-Michel Trivi78ebbab2009-06-02 15:09:51 -07001260 * @param params
1261 * An ArrayList of parameters. The first element of this
1262 * array controls the type of voice to use.
1263 */
Charles Chena9c5e4b2009-07-09 20:27:15 -07001264 public int speak(String callingApp, String text, int queueMode, String[] params) {
Jean-Michel Trivi78ebbab2009-06-02 15:09:51 -07001265 ArrayList<String> speakingParams = new ArrayList<String>();
1266 if (params != null) {
1267 speakingParams = new ArrayList<String>(Arrays.asList(params));
1268 }
Charles Chena9c5e4b2009-07-09 20:27:15 -07001269 return mSelf.speak(callingApp, text, queueMode, speakingParams);
Jean-Michel Trivi78ebbab2009-06-02 15:09:51 -07001270 }
1271
1272 /**
1273 * Plays the earcon using the specified queueing mode and parameters.
1274 *
1275 * @param earcon
1276 * The earcon that should be played
1277 * @param queueMode
Jean-Michel Trivid48ca222009-07-10 10:04:37 -07001278 * TextToSpeech.TTS_QUEUE_FLUSH for no queue (interrupts all previous utterances)
1279 * TextToSpeech.TTS_QUEUE_ADD for queued
Jean-Michel Trivi78ebbab2009-06-02 15:09:51 -07001280 * @param params
1281 * An ArrayList of parameters.
1282 */
Charles Chena9c5e4b2009-07-09 20:27:15 -07001283 public int playEarcon(String callingApp, String earcon, int queueMode, String[] params) {
Jean-Michel Trivi78ebbab2009-06-02 15:09:51 -07001284 ArrayList<String> speakingParams = new ArrayList<String>();
1285 if (params != null) {
1286 speakingParams = new ArrayList<String>(Arrays.asList(params));
1287 }
Charles Chena9c5e4b2009-07-09 20:27:15 -07001288 return mSelf.playEarcon(callingApp, earcon, queueMode, speakingParams);
Jean-Michel Trivi78ebbab2009-06-02 15:09:51 -07001289 }
1290
1291 /**
1292 * Plays the silence using the specified queueing mode and parameters.
1293 *
1294 * @param duration
1295 * The duration of the silence that should be played
1296 * @param queueMode
Jean-Michel Trivid48ca222009-07-10 10:04:37 -07001297 * TextToSpeech.TTS_QUEUE_FLUSH for no queue (interrupts all previous utterances)
1298 * TextToSpeech.TTS_QUEUE_ADD for queued
Jean-Michel Trivi78ebbab2009-06-02 15:09:51 -07001299 * @param params
1300 * An ArrayList of parameters.
1301 */
Charles Chena9c5e4b2009-07-09 20:27:15 -07001302 public int playSilence(String callingApp, long duration, int queueMode, String[] params) {
Jean-Michel Trivi78ebbab2009-06-02 15:09:51 -07001303 ArrayList<String> speakingParams = new ArrayList<String>();
1304 if (params != null) {
1305 speakingParams = new ArrayList<String>(Arrays.asList(params));
1306 }
Charles Chena9c5e4b2009-07-09 20:27:15 -07001307 return mSelf.playSilence(callingApp, duration, queueMode, speakingParams);
Jean-Michel Trivi78ebbab2009-06-02 15:09:51 -07001308 }
1309
Jean-Michel Trivi78ebbab2009-06-02 15:09:51 -07001310 /**
1311 * Stops all speech output and removes any utterances still in the
1312 * queue.
1313 */
Charles Chena9c5e4b2009-07-09 20:27:15 -07001314 public int stop(String callingApp) {
1315 return mSelf.stop(callingApp);
Jean-Michel Trivi78ebbab2009-06-02 15:09:51 -07001316 }
1317
1318 /**
1319 * Returns whether or not the TTS is speaking.
1320 *
1321 * @return Boolean to indicate whether or not the TTS is speaking
1322 */
1323 public boolean isSpeaking() {
1324 return (mSelf.mIsSpeaking && (mSpeechQueue.size() < 1));
1325 }
1326
1327 /**
1328 * Adds a sound resource to the TTS.
1329 *
1330 * @param text
1331 * The text that should be associated with the sound resource
1332 * @param packageName
1333 * The name of the package which has the sound resource
1334 * @param resId
1335 * The resource ID of the sound within its package
1336 */
Charles Chena9c5e4b2009-07-09 20:27:15 -07001337 public void addSpeech(String callingApp, String text, String packageName, int resId) {
1338 mSelf.addSpeech(callingApp, text, packageName, resId);
Jean-Michel Trivi78ebbab2009-06-02 15:09:51 -07001339 }
1340
1341 /**
1342 * Adds a sound resource to the TTS.
1343 *
1344 * @param text
1345 * The text that should be associated with the sound resource
1346 * @param filename
1347 * The filename of the sound resource. This must be a
1348 * complete path like: (/sdcard/mysounds/mysoundbite.mp3).
1349 */
Charles Chena9c5e4b2009-07-09 20:27:15 -07001350 public void addSpeechFile(String callingApp, String text, String filename) {
1351 mSelf.addSpeech(callingApp, text, filename);
Jean-Michel Trivi78ebbab2009-06-02 15:09:51 -07001352 }
1353
1354 /**
1355 * Adds a sound resource to the TTS as an earcon.
1356 *
1357 * @param earcon
1358 * The text that should be associated with the sound resource
1359 * @param packageName
1360 * The name of the package which has the sound resource
1361 * @param resId
1362 * The resource ID of the sound within its package
1363 */
Charles Chena9c5e4b2009-07-09 20:27:15 -07001364 public void addEarcon(String callingApp, String earcon, String packageName, int resId) {
1365 mSelf.addEarcon(callingApp, earcon, packageName, resId);
Jean-Michel Trivi78ebbab2009-06-02 15:09:51 -07001366 }
1367
1368 /**
1369 * Adds a sound resource to the TTS as an earcon.
1370 *
1371 * @param earcon
1372 * The text that should be associated with the sound resource
1373 * @param filename
1374 * The filename of the sound resource. This must be a
1375 * complete path like: (/sdcard/mysounds/mysoundbite.mp3).
1376 */
Charles Chena9c5e4b2009-07-09 20:27:15 -07001377 public void addEarconFile(String callingApp, String earcon, String filename) {
1378 mSelf.addEarcon(callingApp, earcon, filename);
Jean-Michel Trivi78ebbab2009-06-02 15:09:51 -07001379 }
1380
1381 /**
1382 * Sets the speech rate for the TTS. Note that this will only have an
1383 * effect on synthesized speech; it will not affect pre-recorded speech.
1384 *
1385 * @param speechRate
1386 * The speech rate that should be used
1387 */
Charles Chena9c5e4b2009-07-09 20:27:15 -07001388 public int setSpeechRate(String callingApp, int speechRate) {
1389 return mSelf.setSpeechRate(callingApp, speechRate);
Jean-Michel Trivi78ebbab2009-06-02 15:09:51 -07001390 }
1391
Jean-Michel Trivi78ebbab2009-06-02 15:09:51 -07001392 /**
Jean-Michel Trivi2ea53492009-06-23 13:44:40 -07001393 * Sets the pitch for the TTS. Note that this will only have an
1394 * effect on synthesized speech; it will not affect pre-recorded speech.
1395 *
1396 * @param pitch
1397 * The pitch that should be used for the synthesized voice
1398 */
Charles Chena9c5e4b2009-07-09 20:27:15 -07001399 public int setPitch(String callingApp, int pitch) {
1400 return mSelf.setPitch(callingApp, pitch);
Jean-Michel Trivi2ea53492009-06-23 13:44:40 -07001401 }
1402
1403 /**
Jean-Michel Trividdb0a802009-06-29 16:38:47 -07001404 * Returns the level of support for the specified language.
1405 *
1406 * @param lang the three letter ISO language code.
1407 * @param country the three letter ISO country code.
1408 * @param variant the variant code associated with the country and language pair.
1409 * @return one of TTS_LANG_NOT_SUPPORTED, TTS_LANG_MISSING_DATA, TTS_LANG_AVAILABLE,
1410 * TTS_LANG_COUNTRY_AVAILABLE, TTS_LANG_COUNTRY_VAR_AVAILABLE as defined in
1411 * android.speech.tts.TextToSpeech.
1412 */
Charles Chen1a2712c2010-04-01 17:16:28 -07001413 public int isLanguageAvailable(String lang, String country, String variant,
1414 String[] params) {
1415 for (int i = 0; i < params.length - 1; i = i + 2){
1416 String param = params[i];
1417 if (param != null) {
1418 if (param.equals(TextToSpeech.Engine.KEY_PARAM_ENGINE)) {
1419 mSelf.setEngine(params[i + 1]);
1420 break;
1421 }
1422 }
1423 }
Jean-Michel Trividdb0a802009-06-29 16:38:47 -07001424 return mSelf.isLanguageAvailable(lang, country, variant);
1425 }
1426
1427 /**
1428 * Returns the currently set language / country / variant strings representing the
1429 * language used by the TTS engine.
1430 * @return null is no language is set, or an array of 3 string containing respectively
1431 * the language, country and variant.
1432 */
1433 public String[] getLanguage() {
1434 return mSelf.getLanguage();
1435 }
1436
1437 /**
Jean-Michel Trivi679d7282009-06-16 15:36:28 -07001438 * Sets the speech rate for the TTS, which affects the synthesized voice.
Jean-Michel Trivi78ebbab2009-06-02 15:09:51 -07001439 *
Jean-Michel Trivi679d7282009-06-16 15:36:28 -07001440 * @param lang the three letter ISO language code.
1441 * @param country the three letter ISO country code.
1442 * @param variant the variant code associated with the country and language pair.
Jean-Michel Trivi78ebbab2009-06-02 15:09:51 -07001443 */
Charles Chena9c5e4b2009-07-09 20:27:15 -07001444 public int setLanguage(String callingApp, String lang, String country, String variant) {
1445 return mSelf.setLanguage(callingApp, lang, country, variant);
Jean-Michel Trivi78ebbab2009-06-02 15:09:51 -07001446 }
1447
1448 /**
Charles Chen741c25b2009-06-25 17:11:29 -07001449 * Synthesizes the given text to a file using the specified
Jean-Michel Trivi78ebbab2009-06-02 15:09:51 -07001450 * parameters.
1451 *
1452 * @param text
1453 * The String of text that should be synthesized
1454 * @param params
1455 * An ArrayList of parameters. The first element of this
1456 * array controls the type of voice to use.
1457 * @param filename
1458 * The string that gives the full output filename; it should
1459 * be something like "/sdcard/myappsounds/mysound.wav".
1460 * @return A boolean that indicates if the synthesis succeeded
1461 */
Charles Chena9c5e4b2009-07-09 20:27:15 -07001462 public boolean synthesizeToFile(String callingApp, String text, String[] params,
Jean-Michel Trivi78ebbab2009-06-02 15:09:51 -07001463 String filename) {
1464 ArrayList<String> speakingParams = new ArrayList<String>();
1465 if (params != null) {
1466 speakingParams = new ArrayList<String>(Arrays.asList(params));
1467 }
Charles Chena9c5e4b2009-07-09 20:27:15 -07001468 return mSelf.synthesizeToFile(callingApp, text, speakingParams, filename);
Jean-Michel Trivi78ebbab2009-06-02 15:09:51 -07001469 }
Charles Chen741c25b2009-06-25 17:11:29 -07001470
Charles Chenb4fbe762009-11-18 16:34:32 -08001471 /**
1472 * Sets the speech synthesis engine for the TTS by specifying its packagename
1473 *
1474 * @param packageName the packageName of the speech synthesis engine (ie, "com.svox.pico")
1475 *
1476 * @return SUCCESS or ERROR as defined in android.speech.tts.TextToSpeech.
1477 */
Charles Chena19ac0f2010-11-19 10:34:47 -08001478 public int setEngineByPackageName(String packageName) {
1479 return mSelf.setEngine(packageName);
Charles Chenb4fbe762009-11-18 16:34:32 -08001480 }
1481
Charles Chendef71852010-03-25 19:59:50 -07001482 /**
1483 * Returns the packagename of the default speech synthesis engine.
1484 *
1485 * @return Packagename of the TTS engine that the user has chosen as their default.
1486 */
1487 public String getDefaultEngine() {
1488 return mSelf.getDefaultEngine();
1489 }
1490
Charles Chen42229252010-03-29 18:30:30 -07001491 /**
1492 * Returns whether or not the user is forcing their defaults to override the
1493 * Text-To-Speech settings set by applications.
1494 *
1495 * @return Whether or not defaults are enforced.
1496 */
1497 public boolean areDefaultsEnforced() {
1498 return mSelf.isDefaultEnforced();
1499 }
1500
Jean-Michel Trivi78ebbab2009-06-02 15:09:51 -07001501 };
Charles Chena042a742009-05-26 20:55:10 -07001502
1503}