diff options
Diffstat (limited to 'packages/TtsService/src/android/tts/TtsService.java')
-rwxr-xr-x | packages/TtsService/src/android/tts/TtsService.java | 1503 |
1 files changed, 0 insertions, 1503 deletions
diff --git a/packages/TtsService/src/android/tts/TtsService.java b/packages/TtsService/src/android/tts/TtsService.java deleted file mode 100755 index c562327..0000000 --- a/packages/TtsService/src/android/tts/TtsService.java +++ /dev/null @@ -1,1503 +0,0 @@ -/* - * Copyright (C) 2009 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package android.tts; - -import android.app.Service; -import android.content.ContentResolver; -import android.content.Context; -import android.content.Intent; -import android.content.SharedPreferences; -import android.content.pm.ActivityInfo; -import android.content.pm.PackageManager; -import android.content.pm.PackageManager.NameNotFoundException; -import android.content.pm.ResolveInfo; -import android.database.Cursor; -import android.media.AudioManager; -import android.media.MediaPlayer; -import android.media.MediaPlayer.OnCompletionListener; -import android.net.Uri; -import android.os.IBinder; -import android.os.RemoteCallbackList; -import android.os.RemoteException; -import android.preference.PreferenceManager; -import android.speech.tts.ITts.Stub; -import android.speech.tts.ITtsCallback; -import android.speech.tts.TextToSpeech; -import android.util.Log; - -import java.io.File; -import java.io.IOException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Locale; -import java.util.concurrent.locks.ReentrantLock; -import java.util.concurrent.TimeUnit; - - -/** - * @hide Synthesizes speech from text. This is implemented as a service so that - * other applications can call the TTS without needing to bundle the TTS - * in the build. - * - */ -public class TtsService extends Service implements OnCompletionListener { - - private static class SpeechItem { - public static final int TEXT = 0; - public static final int EARCON = 1; - public static final int SILENCE = 2; - public static final int TEXT_TO_FILE = 3; - public String mText = ""; - public ArrayList<String> mParams = null; - public int mType = TEXT; - public long mDuration = 0; - public String mFilename = null; - public String mCallingApp = ""; - - public SpeechItem(String source, String text, ArrayList<String> params, int itemType) { - mText = text; - mParams = params; - mType = itemType; - mCallingApp = source; - } - - public SpeechItem(String source, long silenceTime, ArrayList<String> params) { - mDuration = silenceTime; - mParams = params; - mType = SILENCE; - mCallingApp = source; - } - - public SpeechItem(String source, String text, ArrayList<String> params, - int itemType, String filename) { - mText = text; - mParams = params; - mType = itemType; - mFilename = filename; - mCallingApp = source; - } - - } - - /** - * Contains the information needed to access a sound resource; the name of - * the package that contains the resource and the resID of the resource - * within that package. - */ - private static class SoundResource { - public String mSourcePackageName = null; - public int mResId = -1; - public String mFilename = null; - - public SoundResource(String packageName, int id) { - mSourcePackageName = packageName; - mResId = id; - mFilename = null; - } - - public SoundResource(String file) { - mSourcePackageName = null; - mResId = -1; - mFilename = file; - } - } - // If the speech queue is locked for more than 5 seconds, something has gone - // very wrong with processSpeechQueue. - private static final int SPEECHQUEUELOCK_TIMEOUT = 5000; - private static final int MAX_SPEECH_ITEM_CHAR_LENGTH = 4000; - private static final int MAX_FILENAME_LENGTH = 250; - private static final int DEFAULT_STREAM_TYPE = AudioManager.STREAM_MUSIC; - // TODO use TextToSpeech.DEFAULT_SYNTH once it is unhidden - private static final String DEFAULT_SYNTH = "com.svox.pico"; - private static final String ACTION = "android.intent.action.START_TTS_SERVICE"; - private static final String CATEGORY = "android.intent.category.TTS"; - private static final String PKGNAME = "android.tts"; - protected static final String SERVICE_TAG = "TtsService"; - - private final RemoteCallbackList<ITtsCallback> mCallbacks - = new RemoteCallbackList<ITtsCallback>(); - - private HashMap<String, ITtsCallback> mCallbacksMap; - - private Boolean mIsSpeaking; - private Boolean mSynthBusy; - private ArrayList<SpeechItem> mSpeechQueue; - private HashMap<String, SoundResource> mEarcons; - private HashMap<String, SoundResource> mUtterances; - private MediaPlayer mPlayer; - private SpeechItem mCurrentSpeechItem; - private HashMap<SpeechItem, Boolean> mKillList; // Used to ensure that in-flight synth calls - // are killed when stop is used. - private TtsService mSelf; - - private ContentResolver mResolver; - - // lock for the speech queue (mSpeechQueue) and the current speech item (mCurrentSpeechItem) - private final ReentrantLock speechQueueLock = new ReentrantLock(); - private final ReentrantLock synthesizerLock = new ReentrantLock(); - - private static SynthProxy sNativeSynth = null; - private String currentSpeechEngineSOFile = ""; - - @Override - public void onCreate() { - super.onCreate(); - Log.v("TtsService", "TtsService.onCreate()"); - - mResolver = getContentResolver(); - - currentSpeechEngineSOFile = ""; - setEngine(getDefaultEngine()); - - mSelf = this; - mIsSpeaking = false; - mSynthBusy = false; - - mEarcons = new HashMap<String, SoundResource>(); - mUtterances = new HashMap<String, SoundResource>(); - mCallbacksMap = new HashMap<String, android.speech.tts.ITtsCallback>(); - - mSpeechQueue = new ArrayList<SpeechItem>(); - mPlayer = null; - mCurrentSpeechItem = null; - mKillList = new HashMap<SpeechItem, Boolean>(); - - setDefaultSettings(); - } - - @Override - public void onDestroy() { - super.onDestroy(); - - killAllUtterances(); - - // Don't hog the media player - cleanUpPlayer(); - - if (sNativeSynth != null) { - sNativeSynth.shutdown(); - } - sNativeSynth = null; - - // Unregister all callbacks. - mCallbacks.kill(); - - Log.v(SERVICE_TAG, "onDestroy() completed"); - } - - - private int setEngine(String enginePackageName) { - String soFilename = ""; - if (isDefaultEnforced()) { - enginePackageName = getDefaultEngine(); - } - - // Make sure that the engine has been allowed by the user - if (!enginePackageName.equals(DEFAULT_SYNTH)) { - String[] enabledEngines = android.provider.Settings.Secure.getString(mResolver, - android.provider.Settings.Secure.TTS_ENABLED_PLUGINS).split(" "); - boolean isEnabled = false; - for (int i=0; i<enabledEngines.length; i++) { - if (enabledEngines[i].equals(enginePackageName)) { - isEnabled = true; - break; - } - } - if (!isEnabled) { - // Do not use an engine that the user has not enabled; fall back - // to using the default synthesizer. - enginePackageName = DEFAULT_SYNTH; - } - } - - // The SVOX TTS is an exception to how the TTS packaging scheme works - // because it is part of the system and not a 3rd party add-on; thus - // its binary is actually located under /system/lib/ - if (enginePackageName.equals(DEFAULT_SYNTH)) { - soFilename = "/system/lib/libttspico.so"; - } else { - // Find the package - Intent intent = new Intent("android.intent.action.START_TTS_ENGINE"); - intent.setPackage(enginePackageName); - ResolveInfo[] enginesArray = new ResolveInfo[0]; - PackageManager pm = getPackageManager(); - List <ResolveInfo> resolveInfos = pm.queryIntentActivities(intent, 0); - if ((resolveInfos == null) || resolveInfos.isEmpty()) { - Log.e(SERVICE_TAG, "Invalid TTS Engine Package: " + enginePackageName); - return TextToSpeech.ERROR; - } - enginesArray = resolveInfos.toArray(enginesArray); - // Generate the TTS .so filename from the package - ActivityInfo aInfo = enginesArray[0].activityInfo; - soFilename = aInfo.name.replace(aInfo.packageName + ".", "") + ".so"; - soFilename = soFilename.toLowerCase(); - soFilename = "/data/data/" + aInfo.packageName + "/lib/libtts" + soFilename; - } - - if (currentSpeechEngineSOFile.equals(soFilename)) { - return TextToSpeech.SUCCESS; - } - - File f = new File(soFilename); - if (!f.exists()) { - Log.e(SERVICE_TAG, "Invalid TTS Binary: " + soFilename); - return TextToSpeech.ERROR; - } - - if (sNativeSynth != null) { - sNativeSynth.stopSync(); - sNativeSynth.shutdown(); - sNativeSynth = null; - } - - // Load the engineConfig from the plugin if it has any special configuration - // to be loaded. By convention, if an engine wants the TTS framework to pass - // in any configuration, it must put it into its content provider which has the URI: - // content://<packageName>.providers.SettingsProvider - // That content provider must provide a Cursor which returns the String that - // is to be passed back to the native .so file for the plugin when getString(0) is - // called on it. - // Note that the TTS framework does not care what this String data is: it is something - // that comes from the engine plugin and is consumed only by the engine plugin itself. - String engineConfig = ""; - Cursor c = getContentResolver().query(Uri.parse("content://" + enginePackageName - + ".providers.SettingsProvider"), null, null, null, null); - if (c != null){ - c.moveToFirst(); - engineConfig = c.getString(0); - c.close(); - } - sNativeSynth = new SynthProxy(soFilename, engineConfig); - currentSpeechEngineSOFile = soFilename; - return TextToSpeech.SUCCESS; - } - - - - private void setDefaultSettings() { - setLanguage("", this.getDefaultLanguage(), getDefaultCountry(), getDefaultLocVariant()); - - // speech rate - setSpeechRate("", getDefaultRate()); - } - - - private boolean isDefaultEnforced() { - return (android.provider.Settings.Secure.getInt(mResolver, - android.provider.Settings.Secure.TTS_USE_DEFAULTS, - TextToSpeech.Engine.USE_DEFAULTS) - == 1 ); - } - - private String getDefaultEngine() { - String defaultEngine = android.provider.Settings.Secure.getString(mResolver, - android.provider.Settings.Secure.TTS_DEFAULT_SYNTH); - if (defaultEngine == null) { - return TextToSpeech.Engine.DEFAULT_SYNTH; - } else { - return defaultEngine; - } - } - - private int getDefaultRate() { - return android.provider.Settings.Secure.getInt(mResolver, - android.provider.Settings.Secure.TTS_DEFAULT_RATE, - TextToSpeech.Engine.DEFAULT_RATE); - } - - private int getDefaultPitch() { - // Pitch is not user settable; the default pitch is always 100. - return 100; - } - - private String getDefaultLanguage() { - String defaultLang = android.provider.Settings.Secure.getString(mResolver, - android.provider.Settings.Secure.TTS_DEFAULT_LANG); - if (defaultLang == null) { - // no setting found, use the current Locale to determine the default language - return Locale.getDefault().getISO3Language(); - } else { - return defaultLang; - } - } - - - private String getDefaultCountry() { - String defaultCountry = android.provider.Settings.Secure.getString(mResolver, - android.provider.Settings.Secure.TTS_DEFAULT_COUNTRY); - if (defaultCountry == null) { - // no setting found, use the current Locale to determine the default country - return Locale.getDefault().getISO3Country(); - } else { - return defaultCountry; - } - } - - - private String getDefaultLocVariant() { - String defaultVar = android.provider.Settings.Secure.getString(mResolver, - android.provider.Settings.Secure.TTS_DEFAULT_VARIANT); - if (defaultVar == null) { - // no setting found, use the current Locale to determine the default variant - return Locale.getDefault().getVariant(); - } else { - return defaultVar; - } - } - - - private int setSpeechRate(String callingApp, int rate) { - int res = TextToSpeech.ERROR; - try { - if (isDefaultEnforced()) { - res = sNativeSynth.setSpeechRate(getDefaultRate()); - } else { - res = sNativeSynth.setSpeechRate(rate); - } - } catch (NullPointerException e) { - // synth will become null during onDestroy() - res = TextToSpeech.ERROR; - } - return res; - } - - - private int setPitch(String callingApp, int pitch) { - int res = TextToSpeech.ERROR; - try { - res = sNativeSynth.setPitch(pitch); - } catch (NullPointerException e) { - // synth will become null during onDestroy() - res = TextToSpeech.ERROR; - } - return res; - } - - - private int isLanguageAvailable(String lang, String country, String variant) { - int res = TextToSpeech.LANG_NOT_SUPPORTED; - try { - res = sNativeSynth.isLanguageAvailable(lang, country, variant); - } catch (NullPointerException e) { - // synth will become null during onDestroy() - res = TextToSpeech.LANG_NOT_SUPPORTED; - } - return res; - } - - - private String[] getLanguage() { - try { - return sNativeSynth.getLanguage(); - } catch (Exception e) { - return null; - } - } - - - private int setLanguage(String callingApp, String lang, String country, String variant) { - Log.v(SERVICE_TAG, "TtsService.setLanguage(" + lang + ", " + country + ", " + variant + ")"); - int res = TextToSpeech.ERROR; - try { - if (isDefaultEnforced()) { - res = sNativeSynth.setLanguage(getDefaultLanguage(), getDefaultCountry(), - getDefaultLocVariant()); - } else { - res = sNativeSynth.setLanguage(lang, country, variant); - } - } catch (NullPointerException e) { - // synth will become null during onDestroy() - res = TextToSpeech.ERROR; - } - return res; - } - - - /** - * Adds a sound resource to the TTS. - * - * @param text - * The text that should be associated with the sound resource - * @param packageName - * The name of the package which has the sound resource - * @param resId - * The resource ID of the sound within its package - */ - private void addSpeech(String callingApp, String text, String packageName, int resId) { - mUtterances.put(text, new SoundResource(packageName, resId)); - } - - /** - * Adds a sound resource to the TTS. - * - * @param text - * The text that should be associated with the sound resource - * @param filename - * The filename of the sound resource. This must be a complete - * path like: (/sdcard/mysounds/mysoundbite.mp3). - */ - private void addSpeech(String callingApp, String text, String filename) { - mUtterances.put(text, new SoundResource(filename)); - } - - /** - * Adds a sound resource to the TTS as an earcon. - * - * @param earcon - * The text that should be associated with the sound resource - * @param packageName - * The name of the package which has the sound resource - * @param resId - * The resource ID of the sound within its package - */ - private void addEarcon(String callingApp, String earcon, String packageName, int resId) { - mEarcons.put(earcon, new SoundResource(packageName, resId)); - } - - /** - * Adds a sound resource to the TTS as an earcon. - * - * @param earcon - * The text that should be associated with the sound resource - * @param filename - * The filename of the sound resource. This must be a complete - * path like: (/sdcard/mysounds/mysoundbite.mp3). - */ - private void addEarcon(String callingApp, String earcon, String filename) { - mEarcons.put(earcon, new SoundResource(filename)); - } - - /** - * Speaks the given text using the specified queueing mode and parameters. - * - * @param text - * The text that should be spoken - * @param queueMode - * TextToSpeech.TTS_QUEUE_FLUSH for no queue (interrupts all previous utterances), - * TextToSpeech.TTS_QUEUE_ADD for queued - * @param params - * An ArrayList of parameters. This is not implemented for all - * engines. - */ - private int speak(String callingApp, String text, int queueMode, ArrayList<String> params) { - // Log.v(SERVICE_TAG, "TTS service received " + text); - if (queueMode == TextToSpeech.QUEUE_FLUSH) { - stop(callingApp); - } else if (queueMode == 2) { - stopAll(callingApp); - } - mSpeechQueue.add(new SpeechItem(callingApp, text, params, SpeechItem.TEXT)); - if (!mIsSpeaking) { - processSpeechQueue(); - } - return TextToSpeech.SUCCESS; - } - - /** - * Plays the earcon using the specified queueing mode and parameters. - * - * @param earcon - * The earcon that should be played - * @param queueMode - * TextToSpeech.TTS_QUEUE_FLUSH for no queue (interrupts all previous utterances), - * TextToSpeech.TTS_QUEUE_ADD for queued - * @param params - * An ArrayList of parameters. This is not implemented for all - * engines. - */ - private int playEarcon(String callingApp, String earcon, int queueMode, - ArrayList<String> params) { - if (queueMode == TextToSpeech.QUEUE_FLUSH) { - stop(callingApp); - } else if (queueMode == 2) { - stopAll(callingApp); - } - mSpeechQueue.add(new SpeechItem(callingApp, earcon, params, SpeechItem.EARCON)); - if (!mIsSpeaking) { - processSpeechQueue(); - } - return TextToSpeech.SUCCESS; - } - - /** - * Stops all speech output and removes any utterances still in the queue for the calling app. - */ - private int stop(String callingApp) { - int result = TextToSpeech.ERROR; - boolean speechQueueAvailable = false; - try{ - speechQueueAvailable = - speechQueueLock.tryLock(SPEECHQUEUELOCK_TIMEOUT, TimeUnit.MILLISECONDS); - if (speechQueueAvailable) { - Log.i(SERVICE_TAG, "Stopping"); - for (int i = mSpeechQueue.size() - 1; i > -1; i--){ - if (mSpeechQueue.get(i).mCallingApp.equals(callingApp)){ - mSpeechQueue.remove(i); - } - } - if ((mCurrentSpeechItem != null) && - mCurrentSpeechItem.mCallingApp.equals(callingApp)) { - try { - result = sNativeSynth.stop(); - } catch (NullPointerException e1) { - // synth will become null during onDestroy() - result = TextToSpeech.ERROR; - } - mKillList.put(mCurrentSpeechItem, true); - if (mPlayer != null) { - try { - mPlayer.stop(); - } catch (IllegalStateException e) { - // Do nothing, the player is already stopped. - } - } - mIsSpeaking = false; - mCurrentSpeechItem = null; - } else { - result = TextToSpeech.SUCCESS; - } - Log.i(SERVICE_TAG, "Stopped"); - } else { - Log.e(SERVICE_TAG, "TTS stop(): queue locked longer than expected"); - result = TextToSpeech.ERROR; - } - } catch (InterruptedException e) { - Log.e(SERVICE_TAG, "TTS stop: tryLock interrupted"); - e.printStackTrace(); - } finally { - // This check is needed because finally will always run; even if the - // method returns somewhere in the try block. - if (speechQueueAvailable) { - speechQueueLock.unlock(); - } - return result; - } - } - - - /** - * Stops all speech output, both rendered to a file and directly spoken, and removes any - * utterances still in the queue globally. Files that were being written are deleted. - */ - @SuppressWarnings("finally") - private int killAllUtterances() { - int result = TextToSpeech.ERROR; - boolean speechQueueAvailable = false; - - try { - speechQueueAvailable = speechQueueLock.tryLock(SPEECHQUEUELOCK_TIMEOUT, - TimeUnit.MILLISECONDS); - if (speechQueueAvailable) { - // remove every single entry in the speech queue - mSpeechQueue.clear(); - - // clear the current speech item - if (mCurrentSpeechItem != null) { - result = sNativeSynth.stopSync(); - mKillList.put(mCurrentSpeechItem, true); - mIsSpeaking = false; - - // was the engine writing to a file? - if (mCurrentSpeechItem.mType == SpeechItem.TEXT_TO_FILE) { - // delete the file that was being written - if (mCurrentSpeechItem.mFilename != null) { - File tempFile = new File(mCurrentSpeechItem.mFilename); - Log.v(SERVICE_TAG, "Leaving behind " + mCurrentSpeechItem.mFilename); - if (tempFile.exists()) { - Log.v(SERVICE_TAG, "About to delete " - + mCurrentSpeechItem.mFilename); - if (tempFile.delete()) { - Log.v(SERVICE_TAG, "file successfully deleted"); - } - } - } - } - - mCurrentSpeechItem = null; - } - } else { - Log.e(SERVICE_TAG, "TTS killAllUtterances(): queue locked longer than expected"); - result = TextToSpeech.ERROR; - } - } catch (InterruptedException e) { - Log.e(SERVICE_TAG, "TTS killAllUtterances(): tryLock interrupted"); - result = TextToSpeech.ERROR; - } finally { - // This check is needed because finally will always run, even if the - // method returns somewhere in the try block. - if (speechQueueAvailable) { - speechQueueLock.unlock(); - } - return result; - } - } - - - /** - * Stops all speech output and removes any utterances still in the queue globally, except - * those intended to be synthesized to file. - */ - private int stopAll(String callingApp) { - int result = TextToSpeech.ERROR; - boolean speechQueueAvailable = false; - try{ - speechQueueAvailable = - speechQueueLock.tryLock(SPEECHQUEUELOCK_TIMEOUT, TimeUnit.MILLISECONDS); - if (speechQueueAvailable) { - for (int i = mSpeechQueue.size() - 1; i > -1; i--){ - if (mSpeechQueue.get(i).mType != SpeechItem.TEXT_TO_FILE){ - mSpeechQueue.remove(i); - } - } - if ((mCurrentSpeechItem != null) && - ((mCurrentSpeechItem.mType != SpeechItem.TEXT_TO_FILE) || - mCurrentSpeechItem.mCallingApp.equals(callingApp))) { - try { - result = sNativeSynth.stop(); - } catch (NullPointerException e1) { - // synth will become null during onDestroy() - result = TextToSpeech.ERROR; - } - mKillList.put(mCurrentSpeechItem, true); - if (mPlayer != null) { - try { - mPlayer.stop(); - } catch (IllegalStateException e) { - // Do nothing, the player is already stopped. - } - } - mIsSpeaking = false; - mCurrentSpeechItem = null; - } else { - result = TextToSpeech.SUCCESS; - } - Log.i(SERVICE_TAG, "Stopped all"); - } else { - Log.e(SERVICE_TAG, "TTS stopAll(): queue locked longer than expected"); - result = TextToSpeech.ERROR; - } - } catch (InterruptedException e) { - Log.e(SERVICE_TAG, "TTS stopAll: tryLock interrupted"); - e.printStackTrace(); - } finally { - // This check is needed because finally will always run; even if the - // method returns somewhere in the try block. - if (speechQueueAvailable) { - speechQueueLock.unlock(); - } - return result; - } - } - - public void onCompletion(MediaPlayer arg0) { - // mCurrentSpeechItem may become null if it is stopped at the same - // time it completes. - SpeechItem currentSpeechItemCopy = mCurrentSpeechItem; - if (currentSpeechItemCopy != null) { - String callingApp = currentSpeechItemCopy.mCallingApp; - ArrayList<String> params = currentSpeechItemCopy.mParams; - String utteranceId = ""; - if (params != null) { - for (int i = 0; i < params.size() - 1; i = i + 2) { - String param = params.get(i); - if (param.equals(TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID)) { - utteranceId = params.get(i + 1); - } - } - } - if (utteranceId.length() > 0) { - dispatchUtteranceCompletedCallback(utteranceId, callingApp); - } - } - processSpeechQueue(); - } - - private int playSilence(String callingApp, long duration, int queueMode, - ArrayList<String> params) { - if (queueMode == TextToSpeech.QUEUE_FLUSH) { - stop(callingApp); - } - mSpeechQueue.add(new SpeechItem(callingApp, duration, params)); - if (!mIsSpeaking) { - processSpeechQueue(); - } - return TextToSpeech.SUCCESS; - } - - private void silence(final SpeechItem speechItem) { - class SilenceThread implements Runnable { - public void run() { - String utteranceId = ""; - if (speechItem.mParams != null){ - for (int i = 0; i < speechItem.mParams.size() - 1; i = i + 2){ - String param = speechItem.mParams.get(i); - if (param.equals(TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID)){ - utteranceId = speechItem.mParams.get(i+1); - } - } - } - try { - Thread.sleep(speechItem.mDuration); - } catch (InterruptedException e) { - e.printStackTrace(); - } finally { - if (utteranceId.length() > 0){ - dispatchUtteranceCompletedCallback(utteranceId, speechItem.mCallingApp); - } - processSpeechQueue(); - } - } - } - Thread slnc = (new Thread(new SilenceThread())); - slnc.setPriority(Thread.MIN_PRIORITY); - slnc.start(); - } - - private void speakInternalOnly(final SpeechItem speechItem) { - class SynthThread implements Runnable { - public void run() { - boolean synthAvailable = false; - String utteranceId = ""; - try { - synthAvailable = synthesizerLock.tryLock(); - if (!synthAvailable) { - mSynthBusy = true; - Thread.sleep(100); - Thread synth = (new Thread(new SynthThread())); - synth.start(); - mSynthBusy = false; - return; - } - int streamType = DEFAULT_STREAM_TYPE; - String language = ""; - String country = ""; - String variant = ""; - String speechRate = ""; - String engine = ""; - String pitch = ""; - float volume = TextToSpeech.Engine.DEFAULT_VOLUME; - float pan = TextToSpeech.Engine.DEFAULT_PAN; - if (speechItem.mParams != null){ - for (int i = 0; i < speechItem.mParams.size() - 1; i = i + 2){ - String param = speechItem.mParams.get(i); - if (param != null) { - if (param.equals(TextToSpeech.Engine.KEY_PARAM_RATE)) { - speechRate = speechItem.mParams.get(i+1); - } else if (param.equals(TextToSpeech.Engine.KEY_PARAM_LANGUAGE)){ - language = speechItem.mParams.get(i+1); - } else if (param.equals(TextToSpeech.Engine.KEY_PARAM_COUNTRY)){ - country = speechItem.mParams.get(i+1); - } else if (param.equals(TextToSpeech.Engine.KEY_PARAM_VARIANT)){ - variant = speechItem.mParams.get(i+1); - } else if (param.equals(TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID)){ - utteranceId = speechItem.mParams.get(i+1); - } else if (param.equals(TextToSpeech.Engine.KEY_PARAM_STREAM)) { - try { - streamType - = Integer.parseInt(speechItem.mParams.get(i + 1)); - } catch (NumberFormatException e) { - streamType = DEFAULT_STREAM_TYPE; - } - } else if (param.equals(TextToSpeech.Engine.KEY_PARAM_ENGINE)) { - engine = speechItem.mParams.get(i + 1); - } else if (param.equals(TextToSpeech.Engine.KEY_PARAM_PITCH)) { - pitch = speechItem.mParams.get(i + 1); - } else if (param.equals(TextToSpeech.Engine.KEY_PARAM_VOLUME)) { - try { - volume = Float.parseFloat(speechItem.mParams.get(i + 1)); - } catch (NumberFormatException e) { - volume = TextToSpeech.Engine.DEFAULT_VOLUME; - } - } else if (param.equals(TextToSpeech.Engine.KEY_PARAM_PAN)) { - try { - pan = Float.parseFloat(speechItem.mParams.get(i + 1)); - } catch (NumberFormatException e) { - pan = TextToSpeech.Engine.DEFAULT_PAN; - } - } - } - } - } - // Only do the synthesis if it has not been killed by a subsequent utterance. - if (mKillList.get(speechItem) == null) { - if (engine.length() > 0) { - setEngine(engine); - } else { - setEngine(getDefaultEngine()); - } - if (language.length() > 0){ - setLanguage("", language, country, variant); - } else { - setLanguage("", getDefaultLanguage(), getDefaultCountry(), - getDefaultLocVariant()); - } - if (speechRate.length() > 0){ - setSpeechRate("", Integer.parseInt(speechRate)); - } else { - setSpeechRate("", getDefaultRate()); - } - if (pitch.length() > 0){ - setPitch("", Integer.parseInt(pitch)); - } else { - setPitch("", getDefaultPitch()); - } - try { - sNativeSynth.speak(speechItem.mText, streamType, volume, pan); - } catch (NullPointerException e) { - // synth will become null during onDestroy() - Log.v(SERVICE_TAG, " null synth, can't speak"); - } - } - } catch (InterruptedException e) { - Log.e(SERVICE_TAG, "TTS speakInternalOnly(): tryLock interrupted"); - e.printStackTrace(); - } finally { - // This check is needed because finally will always run; - // even if the - // method returns somewhere in the try block. - if (utteranceId.length() > 0){ - dispatchUtteranceCompletedCallback(utteranceId, speechItem.mCallingApp); - } - if (synthAvailable) { - synthesizerLock.unlock(); - processSpeechQueue(); - } - } - } - } - Thread synth = (new Thread(new SynthThread())); - synth.setPriority(Thread.MAX_PRIORITY); - synth.start(); - } - - private void synthToFileInternalOnly(final SpeechItem speechItem) { - class SynthThread implements Runnable { - public void run() { - boolean synthAvailable = false; - String utteranceId = ""; - Log.i(SERVICE_TAG, "Synthesizing to " + speechItem.mFilename); - try { - synthAvailable = synthesizerLock.tryLock(); - if (!synthAvailable) { - synchronized (this) { - mSynthBusy = true; - } - Thread.sleep(100); - Thread synth = (new Thread(new SynthThread())); - synth.start(); - synchronized (this) { - mSynthBusy = false; - } - return; - } - String language = ""; - String country = ""; - String variant = ""; - String speechRate = ""; - String engine = ""; - String pitch = ""; - if (speechItem.mParams != null){ - for (int i = 0; i < speechItem.mParams.size() - 1; i = i + 2){ - String param = speechItem.mParams.get(i); - if (param != null) { - if (param.equals(TextToSpeech.Engine.KEY_PARAM_RATE)) { - speechRate = speechItem.mParams.get(i+1); - } else if (param.equals(TextToSpeech.Engine.KEY_PARAM_LANGUAGE)){ - language = speechItem.mParams.get(i+1); - } else if (param.equals(TextToSpeech.Engine.KEY_PARAM_COUNTRY)){ - country = speechItem.mParams.get(i+1); - } else if (param.equals(TextToSpeech.Engine.KEY_PARAM_VARIANT)){ - variant = speechItem.mParams.get(i+1); - } else if (param.equals(TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID)){ - utteranceId = speechItem.mParams.get(i+1); - } else if (param.equals(TextToSpeech.Engine.KEY_PARAM_ENGINE)) { - engine = speechItem.mParams.get(i + 1); - } else if (param.equals(TextToSpeech.Engine.KEY_PARAM_PITCH)) { - pitch = speechItem.mParams.get(i + 1); - } - } - } - } - // Only do the synthesis if it has not been killed by a subsequent utterance. - if (mKillList.get(speechItem) == null){ - if (engine.length() > 0) { - setEngine(engine); - } else { - setEngine(getDefaultEngine()); - } - if (language.length() > 0){ - setLanguage("", language, country, variant); - } else { - setLanguage("", getDefaultLanguage(), getDefaultCountry(), - getDefaultLocVariant()); - } - if (speechRate.length() > 0){ - setSpeechRate("", Integer.parseInt(speechRate)); - } else { - setSpeechRate("", getDefaultRate()); - } - if (pitch.length() > 0){ - setPitch("", Integer.parseInt(pitch)); - } else { - setPitch("", getDefaultPitch()); - } - try { - sNativeSynth.synthesizeToFile(speechItem.mText, speechItem.mFilename); - } catch (NullPointerException e) { - // synth will become null during onDestroy() - Log.v(SERVICE_TAG, " null synth, can't synthesize to file"); - } - } - } catch (InterruptedException e) { - Log.e(SERVICE_TAG, "TTS synthToFileInternalOnly(): tryLock interrupted"); - e.printStackTrace(); - } finally { - // This check is needed because finally will always run; - // even if the - // method returns somewhere in the try block. - if (utteranceId.length() > 0){ - dispatchUtteranceCompletedCallback(utteranceId, speechItem.mCallingApp); - } - if (synthAvailable) { - synthesizerLock.unlock(); - processSpeechQueue(); - } - } - } - } - Thread synth = (new Thread(new SynthThread())); - synth.setPriority(Thread.MAX_PRIORITY); - synth.start(); - } - - private SoundResource getSoundResource(SpeechItem speechItem) { - SoundResource sr = null; - String text = speechItem.mText; - if (speechItem.mType == SpeechItem.SILENCE) { - // Do nothing if this is just silence - } else if (speechItem.mType == SpeechItem.EARCON) { - sr = mEarcons.get(text); - } else { - sr = mUtterances.get(text); - } - return sr; - } - - private void broadcastTtsQueueProcessingCompleted(){ - Intent i = new Intent(TextToSpeech.ACTION_TTS_QUEUE_PROCESSING_COMPLETED); - sendBroadcast(i); - } - - - private void dispatchUtteranceCompletedCallback(String utteranceId, String packageName) { - ITtsCallback cb = mCallbacksMap.get(packageName); - if (cb == null){ - return; - } - Log.v(SERVICE_TAG, "TTS callback: dispatch started"); - // Broadcast to all clients the new value. - final int N = mCallbacks.beginBroadcast(); - try { - cb.utteranceCompleted(utteranceId); - } catch (RemoteException e) { - // The RemoteCallbackList will take care of removing - // the dead object for us. - } - mCallbacks.finishBroadcast(); - Log.v(SERVICE_TAG, "TTS callback: dispatch completed to " + N); - } - - private SpeechItem splitCurrentTextIfNeeded(SpeechItem currentSpeechItem){ - if (currentSpeechItem.mText.length() < MAX_SPEECH_ITEM_CHAR_LENGTH){ - return currentSpeechItem; - } else { - String callingApp = currentSpeechItem.mCallingApp; - ArrayList<SpeechItem> splitItems = new ArrayList<SpeechItem>(); - int start = 0; - int end = start + MAX_SPEECH_ITEM_CHAR_LENGTH - 1; - String splitText; - SpeechItem splitItem; - while (end < currentSpeechItem.mText.length()){ - splitText = currentSpeechItem.mText.substring(start, end); - splitItem = new SpeechItem(callingApp, splitText, null, SpeechItem.TEXT); - splitItems.add(splitItem); - start = end; - end = start + MAX_SPEECH_ITEM_CHAR_LENGTH - 1; - } - splitText = currentSpeechItem.mText.substring(start); - splitItem = new SpeechItem(callingApp, splitText, null, SpeechItem.TEXT); - splitItems.add(splitItem); - mSpeechQueue.remove(0); - for (int i = splitItems.size() - 1; i >= 0; i--){ - mSpeechQueue.add(0, splitItems.get(i)); - } - return mSpeechQueue.get(0); - } - } - - private void processSpeechQueue() { - boolean speechQueueAvailable = false; - synchronized (this) { - if (mSynthBusy){ - // There is already a synth thread waiting to run. - return; - } - } - try { - speechQueueAvailable = - speechQueueLock.tryLock(SPEECHQUEUELOCK_TIMEOUT, TimeUnit.MILLISECONDS); - if (!speechQueueAvailable) { - Log.e(SERVICE_TAG, "processSpeechQueue - Speech queue is unavailable."); - return; - } - if (mSpeechQueue.size() < 1) { - mIsSpeaking = false; - mKillList.clear(); - broadcastTtsQueueProcessingCompleted(); - return; - } - - mCurrentSpeechItem = mSpeechQueue.get(0); - mIsSpeaking = true; - SoundResource sr = getSoundResource(mCurrentSpeechItem); - // Synth speech as needed - synthesizer should call - // processSpeechQueue to continue running the queue - // Log.v(SERVICE_TAG, "TTS processing: " + mCurrentSpeechItem.mText); - if (sr == null) { - if (mCurrentSpeechItem.mType == SpeechItem.TEXT) { - mCurrentSpeechItem = splitCurrentTextIfNeeded(mCurrentSpeechItem); - speakInternalOnly(mCurrentSpeechItem); - } else if (mCurrentSpeechItem.mType == SpeechItem.TEXT_TO_FILE) { - synthToFileInternalOnly(mCurrentSpeechItem); - } else { - // This is either silence or an earcon that was missing - silence(mCurrentSpeechItem); - } - } else { - cleanUpPlayer(); - if (sr.mSourcePackageName == PKGNAME) { - // Utterance is part of the TTS library - mPlayer = MediaPlayer.create(this, sr.mResId); - } else if (sr.mSourcePackageName != null) { - // Utterance is part of the app calling the library - Context ctx; - try { - ctx = this.createPackageContext(sr.mSourcePackageName, 0); - } catch (NameNotFoundException e) { - e.printStackTrace(); - mSpeechQueue.remove(0); // Remove it from the queue and - // move on - mIsSpeaking = false; - return; - } - mPlayer = MediaPlayer.create(ctx, sr.mResId); - } else { - // Utterance is coming from a file - mPlayer = MediaPlayer.create(this, Uri.parse(sr.mFilename)); - } - - // Check if Media Server is dead; if it is, clear the queue and - // give up for now - hopefully, it will recover itself. - if (mPlayer == null) { - mSpeechQueue.clear(); - mIsSpeaking = false; - return; - } - mPlayer.setOnCompletionListener(this); - try { - mPlayer.setAudioStreamType(getStreamTypeFromParams(mCurrentSpeechItem.mParams)); - mPlayer.start(); - } catch (IllegalStateException e) { - mSpeechQueue.clear(); - mIsSpeaking = false; - cleanUpPlayer(); - return; - } - } - if (mSpeechQueue.size() > 0) { - mSpeechQueue.remove(0); - } - } catch (InterruptedException e) { - Log.e(SERVICE_TAG, "TTS processSpeechQueue: tryLock interrupted"); - e.printStackTrace(); - } finally { - // This check is needed because finally will always run; even if the - // method returns somewhere in the try block. - if (speechQueueAvailable) { - speechQueueLock.unlock(); - } - } - } - - private int getStreamTypeFromParams(ArrayList<String> paramList) { - int streamType = DEFAULT_STREAM_TYPE; - if (paramList == null) { - return streamType; - } - for (int i = 0; i < paramList.size() - 1; i = i + 2) { - String param = paramList.get(i); - if ((param != null) && (param.equals(TextToSpeech.Engine.KEY_PARAM_STREAM))) { - try { - streamType = Integer.parseInt(paramList.get(i + 1)); - } catch (NumberFormatException e) { - streamType = DEFAULT_STREAM_TYPE; - } - } - } - return streamType; - } - - private void cleanUpPlayer() { - if (mPlayer != null) { - mPlayer.release(); - mPlayer = null; - } - } - - /** - * Synthesizes the given text to a file using the specified parameters. - * - * @param text - * The String of text that should be synthesized - * @param params - * An ArrayList of parameters. The first element of this array - * controls the type of voice to use. - * @param filename - * The string that gives the full output filename; it should be - * something like "/sdcard/myappsounds/mysound.wav". - * @return A boolean that indicates if the synthesis can be started - */ - private boolean synthesizeToFile(String callingApp, String text, ArrayList<String> params, - String filename) { - // Don't allow a filename that is too long - if (filename.length() > MAX_FILENAME_LENGTH) { - return false; - } - // Don't allow anything longer than the max text length; since this - // is synthing to a file, don't even bother splitting it. - if (text.length() >= MAX_SPEECH_ITEM_CHAR_LENGTH){ - return false; - } - // Check that the output file can be created - try { - File tempFile = new File(filename); - if (tempFile.exists()) { - Log.v("TtsService", "File " + filename + " exists, deleting."); - tempFile.delete(); - } - if (!tempFile.createNewFile()) { - Log.e("TtsService", "Unable to synthesize to file: can't create " + filename); - return false; - } - tempFile.delete(); - } catch (IOException e) { - Log.e("TtsService", "Can't create " + filename + " due to exception " + e); - return false; - } - mSpeechQueue.add(new SpeechItem(callingApp, text, params, SpeechItem.TEXT_TO_FILE, filename)); - if (!mIsSpeaking) { - processSpeechQueue(); - } - return true; - } - - @Override - public IBinder onBind(Intent intent) { - if (ACTION.equals(intent.getAction())) { - for (String category : intent.getCategories()) { - if (category.equals(CATEGORY)) { - return mBinder; - } - } - } - return null; - } - - private final android.speech.tts.ITts.Stub mBinder = new Stub() { - - public int registerCallback(String packageName, ITtsCallback cb) { - if (cb != null) { - mCallbacks.register(cb); - mCallbacksMap.put(packageName, cb); - return TextToSpeech.SUCCESS; - } - return TextToSpeech.ERROR; - } - - public int unregisterCallback(String packageName, ITtsCallback cb) { - if (cb != null) { - mCallbacksMap.remove(packageName); - mCallbacks.unregister(cb); - return TextToSpeech.SUCCESS; - } - return TextToSpeech.ERROR; - } - - /** - * Speaks the given text using the specified queueing mode and - * parameters. - * - * @param text - * The text that should be spoken - * @param queueMode - * TextToSpeech.TTS_QUEUE_FLUSH for no queue (interrupts all previous utterances) - * TextToSpeech.TTS_QUEUE_ADD for queued - * @param params - * An ArrayList of parameters. The first element of this - * array controls the type of voice to use. - */ - public int speak(String callingApp, String text, int queueMode, String[] params) { - ArrayList<String> speakingParams = new ArrayList<String>(); - if (params != null) { - speakingParams = new ArrayList<String>(Arrays.asList(params)); - } - return mSelf.speak(callingApp, text, queueMode, speakingParams); - } - - /** - * Plays the earcon using the specified queueing mode and parameters. - * - * @param earcon - * The earcon that should be played - * @param queueMode - * TextToSpeech.TTS_QUEUE_FLUSH for no queue (interrupts all previous utterances) - * TextToSpeech.TTS_QUEUE_ADD for queued - * @param params - * An ArrayList of parameters. - */ - public int playEarcon(String callingApp, String earcon, int queueMode, String[] params) { - ArrayList<String> speakingParams = new ArrayList<String>(); - if (params != null) { - speakingParams = new ArrayList<String>(Arrays.asList(params)); - } - return mSelf.playEarcon(callingApp, earcon, queueMode, speakingParams); - } - - /** - * Plays the silence using the specified queueing mode and parameters. - * - * @param duration - * The duration of the silence that should be played - * @param queueMode - * TextToSpeech.TTS_QUEUE_FLUSH for no queue (interrupts all previous utterances) - * TextToSpeech.TTS_QUEUE_ADD for queued - * @param params - * An ArrayList of parameters. - */ - public int playSilence(String callingApp, long duration, int queueMode, String[] params) { - ArrayList<String> speakingParams = new ArrayList<String>(); - if (params != null) { - speakingParams = new ArrayList<String>(Arrays.asList(params)); - } - return mSelf.playSilence(callingApp, duration, queueMode, speakingParams); - } - - /** - * Stops all speech output and removes any utterances still in the - * queue. - */ - public int stop(String callingApp) { - return mSelf.stop(callingApp); - } - - /** - * Returns whether or not the TTS is speaking. - * - * @return Boolean to indicate whether or not the TTS is speaking - */ - public boolean isSpeaking() { - return (mSelf.mIsSpeaking && (mSpeechQueue.size() < 1)); - } - - /** - * Adds a sound resource to the TTS. - * - * @param text - * The text that should be associated with the sound resource - * @param packageName - * The name of the package which has the sound resource - * @param resId - * The resource ID of the sound within its package - */ - public void addSpeech(String callingApp, String text, String packageName, int resId) { - mSelf.addSpeech(callingApp, text, packageName, resId); - } - - /** - * Adds a sound resource to the TTS. - * - * @param text - * The text that should be associated with the sound resource - * @param filename - * The filename of the sound resource. This must be a - * complete path like: (/sdcard/mysounds/mysoundbite.mp3). - */ - public void addSpeechFile(String callingApp, String text, String filename) { - mSelf.addSpeech(callingApp, text, filename); - } - - /** - * Adds a sound resource to the TTS as an earcon. - * - * @param earcon - * The text that should be associated with the sound resource - * @param packageName - * The name of the package which has the sound resource - * @param resId - * The resource ID of the sound within its package - */ - public void addEarcon(String callingApp, String earcon, String packageName, int resId) { - mSelf.addEarcon(callingApp, earcon, packageName, resId); - } - - /** - * Adds a sound resource to the TTS as an earcon. - * - * @param earcon - * The text that should be associated with the sound resource - * @param filename - * The filename of the sound resource. This must be a - * complete path like: (/sdcard/mysounds/mysoundbite.mp3). - */ - public void addEarconFile(String callingApp, String earcon, String filename) { - mSelf.addEarcon(callingApp, earcon, filename); - } - - /** - * Sets the speech rate for the TTS. Note that this will only have an - * effect on synthesized speech; it will not affect pre-recorded speech. - * - * @param speechRate - * The speech rate that should be used - */ - public int setSpeechRate(String callingApp, int speechRate) { - return mSelf.setSpeechRate(callingApp, speechRate); - } - - /** - * Sets the pitch for the TTS. Note that this will only have an - * effect on synthesized speech; it will not affect pre-recorded speech. - * - * @param pitch - * The pitch that should be used for the synthesized voice - */ - public int setPitch(String callingApp, int pitch) { - return mSelf.setPitch(callingApp, pitch); - } - - /** - * Returns the level of support for the specified language. - * - * @param lang the three letter ISO language code. - * @param country the three letter ISO country code. - * @param variant the variant code associated with the country and language pair. - * @return one of TTS_LANG_NOT_SUPPORTED, TTS_LANG_MISSING_DATA, TTS_LANG_AVAILABLE, - * TTS_LANG_COUNTRY_AVAILABLE, TTS_LANG_COUNTRY_VAR_AVAILABLE as defined in - * android.speech.tts.TextToSpeech. - */ - public int isLanguageAvailable(String lang, String country, String variant, - String[] params) { - for (int i = 0; i < params.length - 1; i = i + 2){ - String param = params[i]; - if (param != null) { - if (param.equals(TextToSpeech.Engine.KEY_PARAM_ENGINE)) { - mSelf.setEngine(params[i + 1]); - break; - } - } - } - return mSelf.isLanguageAvailable(lang, country, variant); - } - - /** - * Returns the currently set language / country / variant strings representing the - * language used by the TTS engine. - * @return null is no language is set, or an array of 3 string containing respectively - * the language, country and variant. - */ - public String[] getLanguage() { - return mSelf.getLanguage(); - } - - /** - * Sets the speech rate for the TTS, which affects the synthesized voice. - * - * @param lang the three letter ISO language code. - * @param country the three letter ISO country code. - * @param variant the variant code associated with the country and language pair. - */ - public int setLanguage(String callingApp, String lang, String country, String variant) { - return mSelf.setLanguage(callingApp, lang, country, variant); - } - - /** - * Synthesizes the given text to a file using the specified - * parameters. - * - * @param text - * The String of text that should be synthesized - * @param params - * An ArrayList of parameters. The first element of this - * array controls the type of voice to use. - * @param filename - * The string that gives the full output filename; it should - * be something like "/sdcard/myappsounds/mysound.wav". - * @return A boolean that indicates if the synthesis succeeded - */ - public boolean synthesizeToFile(String callingApp, String text, String[] params, - String filename) { - ArrayList<String> speakingParams = new ArrayList<String>(); - if (params != null) { - speakingParams = new ArrayList<String>(Arrays.asList(params)); - } - return mSelf.synthesizeToFile(callingApp, text, speakingParams, filename); - } - - /** - * Sets the speech synthesis engine for the TTS by specifying its packagename - * - * @param packageName the packageName of the speech synthesis engine (ie, "com.svox.pico") - * - * @return SUCCESS or ERROR as defined in android.speech.tts.TextToSpeech. - */ - public int setEngineByPackageName(String packageName) { - return mSelf.setEngine(packageName); - } - - /** - * Returns the packagename of the default speech synthesis engine. - * - * @return Packagename of the TTS engine that the user has chosen as their default. - */ - public String getDefaultEngine() { - return mSelf.getDefaultEngine(); - } - - /** - * Returns whether or not the user is forcing their defaults to override the - * Text-To-Speech settings set by applications. - * - * @return Whether or not defaults are enforced. - */ - public boolean areDefaultsEnforced() { - return mSelf.isDefaultEnforced(); - } - - }; - -} |