diff options
author | Jean-Michel Trivi <jmtrivi@google.com> | 2009-06-02 15:09:51 -0700 |
---|---|---|
committer | Jean-Michel Trivi <jmtrivi@google.com> | 2009-06-02 15:09:51 -0700 |
commit | 78ebbabfe18f2c6fb4825e4bdbb1613e0901f0f3 (patch) | |
tree | a6ce14898983c0fef8088c341bb3fbedce053486 /tts | |
parent | b42e1ff65ae44a0873959c5defe3ba4826109490 (diff) | |
download | frameworks_base-78ebbabfe18f2c6fb4825e4bdbb1613e0901f0f3.zip frameworks_base-78ebbabfe18f2c6fb4825e4bdbb1613e0901f0f3.tar.gz frameworks_base-78ebbabfe18f2c6fb4825e4bdbb1613e0901f0f3.tar.bz2 |
Addressed comments of change 2515 for the TtsService class:
- made the SpeechItem and SoundResource inner classes static,
- prefixed the TtsService member variables by 'm',
- changed indentation from 2 to 4 characters.
Diffstat (limited to 'tts')
-rwxr-xr-x | tts/java/android/tts/TtsService.java | 1270 |
1 files changed, 635 insertions, 635 deletions
diff --git a/tts/java/android/tts/TtsService.java b/tts/java/android/tts/TtsService.java index 4b794db..d317181 100755 --- a/tts/java/android/tts/TtsService.java +++ b/tts/java/android/tts/TtsService.java @@ -45,635 +45,158 @@ import java.util.concurrent.locks.ReentrantLock; */ public class TtsService extends Service implements OnCompletionListener { - private class SpeechItem { - public static final int SPEECH = 0; - public static final int EARCON = 1; - public static final int SILENCE = 2; - public String mText = null; - public ArrayList<String> mParams = null; - public int mType = SPEECH; - public long mDuration = 0; - - public SpeechItem(String text, ArrayList<String> params, int itemType) { - mText = text; - mParams = params; - mType = itemType; - } - - public SpeechItem(long silenceTime) { - mDuration = silenceTime; - } - } - - /** - * 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 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; - } + private static class SpeechItem { + public static final int SPEECH = 0; + public static final int EARCON = 1; + public static final int SILENCE = 2; + public String mText = null; + public ArrayList<String> mParams = null; + public int mType = SPEECH; + public long mDuration = 0; + + public SpeechItem(String text, ArrayList<String> params, int itemType) { + mText = text; + mParams = params; + mType = itemType; + } - public SoundResource(String file) { - mSourcePackageName = null; - mResId = -1; - mFilename = file; + public SpeechItem(long silenceTime) { + mDuration = silenceTime; + } } - } - private static final String ACTION = "android.intent.action.USE_TTS"; - private static final String CATEGORY = "android.intent.category.TTS"; - private static final String PKGNAME = "android.tts"; - - final RemoteCallbackList<ITtsCallback> mCallbacks = new RemoteCallbackList<ITtsCallback>(); - - private Boolean isSpeaking; - private ArrayList<SpeechItem> speechQueue; - private HashMap<String, SoundResource> earcons; - private HashMap<String, SoundResource> utterances; - private MediaPlayer player; - private TtsService self; + /** + * 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; + } - private SharedPreferences prefs; + public SoundResource(String file) { + mSourcePackageName = null; + mResId = -1; + mFilename = file; + } + } - private final ReentrantLock speechQueueLock = new ReentrantLock(); - private final ReentrantLock synthesizerLock = new ReentrantLock(); + private static final String ACTION = "android.intent.action.USE_TTS"; + private static final String CATEGORY = "android.intent.category.TTS"; + private static final String PKGNAME = "android.tts"; - // TODO support multiple SpeechSynthesis objects - private SynthProxy nativeSynth; + final RemoteCallbackList<ITtsCallback> mCallbacks = new RemoteCallbackList<ITtsCallback>(); - @Override - public void onCreate() { - super.onCreate(); - Log.i("TTS", "TTS starting"); + private Boolean mIsSpeaking; + private ArrayList<SpeechItem> mSpeechQueue; + private HashMap<String, SoundResource> mEarcons; + private HashMap<String, SoundResource> mUtterances; + private MediaPlayer mPlayer; + private TtsService mSelf; + private SharedPreferences prefs; - // TODO: Make this work when the settings are done in the main Settings - // app. - prefs = PreferenceManager.getDefaultSharedPreferences(this); + private final ReentrantLock speechQueueLock = new ReentrantLock(); + private final ReentrantLock synthesizerLock = new ReentrantLock(); - // TODO: This should be changed to work by requesting the path - // from the default engine. - nativeSynth = new SynthProxy(prefs.getString("engine_pref", "")); + // TODO support multiple SpeechSynthesis objects + private SynthProxy nativeSynth; + @Override + public void onCreate() { + super.onCreate(); + Log.i("TTS", "TTS starting"); - self = this; - isSpeaking = false; - earcons = new HashMap<String, SoundResource>(); - utterances = new HashMap<String, SoundResource>(); + // TODO: Make this work when the settings are done in the main Settings + // app. + prefs = PreferenceManager.getDefaultSharedPreferences(this); - speechQueue = new ArrayList<SpeechItem>(); - player = null; + // TODO: This should be changed to work by requesting the path + // from the default engine. + nativeSynth = new SynthProxy(prefs.getString("engine_pref", "")); - setLanguage(prefs.getString("lang_pref", "en-rUS")); - setSpeechRate(Integer.parseInt(prefs.getString("rate_pref", "140"))); - } - @Override - public void onDestroy() { - super.onDestroy(); - // Don't hog the media player - cleanUpPlayer(); + mSelf = this; + mIsSpeaking = false; - nativeSynth.shutdown(); + mEarcons = new HashMap<String, SoundResource>(); + mUtterances = new HashMap<String, SoundResource>(); - // Unregister all callbacks. - mCallbacks.kill(); - } + mSpeechQueue = new ArrayList<SpeechItem>(); + mPlayer = null; - private void setSpeechRate(int rate) { - if (prefs.getBoolean("override_pref", false)) { - // This is set to the default here so that the preview in the prefs - // activity will show the change without a restart, even if apps are - // not allowed to change the defaults. - rate = Integer.parseInt(prefs.getString("rate_pref", "140")); - } - nativeSynth.setSpeechRate(rate); - } - - private void setLanguage(String lang) { - if (prefs.getBoolean("override_pref", false)) { - // This is set to the default here so that the preview in the prefs - // activity will show the change without a restart, even if apps are - // not - // allowed to change the defaults. - lang = prefs.getString("lang_pref", "en-rUS"); + setLanguage(prefs.getString("lang_pref", "en-rUS")); + setSpeechRate(Integer.parseInt(prefs.getString("rate_pref", "140"))); } - nativeSynth.setLanguage(lang); - } - private void setEngine(String engineName, String[] requestedLanguages, - int strictness) { - // TODO: Implement engine selection code here. - Intent engineIntent = new Intent( - "android.intent.action.START_TTS_ENGINE"); - if (engineName != null) { - engineIntent.addCategory("android.intent.action.tts_engine." - + engineName); - } - for (int i = 0; i < requestedLanguages.length; i++) { - engineIntent.addCategory("android.intent.action.tts_lang." - + requestedLanguages[i]); - } - ResolveInfo[] enginesArray = new ResolveInfo[0]; - PackageManager pm = getPackageManager(); - enginesArray = pm.queryIntentActivities(engineIntent, 0).toArray( - enginesArray); - } - - private void setEngine(Intent engineIntent) { - // TODO: Implement engine selection code here. - } - - private int getEngineStatus() { - // TODO: Proposal - add a sanity check method that - // TTS engine plugins must implement. - return 0; - } - - /** - * 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 text, String packageName, int resId) { - utterances.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 text, String filename) { - utterances.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 earcon, String packageName, int resId) { - earcons.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 earcon, String filename) { - earcons.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 - * 0 for no queue (interrupts all previous utterances), 1 for - * queued - * @param params - * An ArrayList of parameters. This is not implemented for all - * engines. - */ - private void speak(String text, int queueMode, ArrayList<String> params) { - if (queueMode == 0) { - stop(); - } - speechQueue.add(new SpeechItem(text, params, SpeechItem.SPEECH)); - if (!isSpeaking) { - processSpeechQueue(); - } - } - - /** - * Plays the earcon using the specified queueing mode and parameters. - * - * @param earcon - * The earcon that should be played - * @param queueMode - * 0 for no queue (interrupts all previous utterances), 1 for - * queued - * @param params - * An ArrayList of parameters. This is not implemented for all - * engines. - */ - private void playEarcon(String earcon, int queueMode, - ArrayList<String> params) { - if (queueMode == 0) { - stop(); - } - speechQueue.add(new SpeechItem(earcon, params, SpeechItem.EARCON)); - if (!isSpeaking) { - processSpeechQueue(); - } - } - - /** - * Stops all speech output and removes any utterances still in the queue. - */ - private void stop() { - Log.i("TTS", "Stopping"); - speechQueue.clear(); - - nativeSynth.stop(); - isSpeaking = false; - if (player != null) { - try { - player.stop(); - } catch (IllegalStateException e) { - // Do nothing, the player is already stopped. - } - } - Log.i("TTS", "Stopped"); - } + @Override + public void onDestroy() { + super.onDestroy(); + // Don't hog the media player + cleanUpPlayer(); - public void onCompletion(MediaPlayer arg0) { - processSpeechQueue(); - } + nativeSynth.shutdown(); - private void playSilence(long duration, int queueMode, - ArrayList<String> params) { - if (queueMode == 0) { - stop(); + // Unregister all callbacks. + mCallbacks.kill(); } - speechQueue.add(new SpeechItem(duration)); - if (!isSpeaking) { - processSpeechQueue(); - } - } - private void silence(final long duration) { - class SilenceThread implements Runnable { - public void run() { - try { - Thread.sleep(duration); - } catch (InterruptedException e) { - e.printStackTrace(); - } finally { - processSpeechQueue(); + private void setSpeechRate(int rate) { + if (prefs.getBoolean("override_pref", false)) { + // This is set to the default here so that the preview in the prefs + // activity will show the change without a restart, even if apps are + // not allowed to change the defaults. + rate = Integer.parseInt(prefs.getString("rate_pref", "140")); } - } - } - Thread slnc = (new Thread(new SilenceThread())); - slnc.setPriority(Thread.MIN_PRIORITY); - slnc.start(); - } - - private void speakInternalOnly(final String text, - final ArrayList<String> params) { - class SynthThread implements Runnable { - public void run() { - boolean synthAvailable = false; - try { - synthAvailable = synthesizerLock.tryLock(); - if (!synthAvailable) { - Thread.sleep(100); - Thread synth = (new Thread(new SynthThread())); - synth.setPriority(Thread.MIN_PRIORITY); - synth.start(); - return; - } - nativeSynth.speak(text); - } catch (InterruptedException e) { - e.printStackTrace(); - } finally { - // This check is needed because finally will always run; - // even if the - // method returns somewhere in the try block. - if (synthAvailable) { - synthesizerLock.unlock(); - } - } - } - } - Thread synth = (new Thread(new SynthThread())); - synth.setPriority(Thread.MIN_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 = earcons.get(text); - } else { - sr = utterances.get(text); - } - return sr; - } - - private void dispatchSpeechCompletedCallbacks(String mark) { - Log.i("TTS callback", "dispatch started"); - // Broadcast to all clients the new value. - final int N = mCallbacks.beginBroadcast(); - for (int i = 0; i < N; i++) { - try { - mCallbacks.getBroadcastItem(i).markReached(mark); - } catch (RemoteException e) { - // The RemoteCallbackList will take care of removing - // the dead object for us. - } + nativeSynth.setSpeechRate(rate); } - mCallbacks.finishBroadcast(); - Log.i("TTS callback", "dispatch completed to " + N); - } - - private void processSpeechQueue() { - boolean speechQueueAvailable = false; - try { - speechQueueAvailable = speechQueueLock.tryLock(); - if (!speechQueueAvailable) { - return; - } - if (speechQueue.size() < 1) { - isSpeaking = false; - // Dispatch a completion here as this is the - // only place where speech completes normally. - // Nothing left to say in the queue is a special case - // that is always a "mark" - associated text is null. - dispatchSpeechCompletedCallbacks(""); - return; - } - - SpeechItem currentSpeechItem = speechQueue.get(0); - isSpeaking = true; - SoundResource sr = getSoundResource(currentSpeechItem); - // Synth speech as needed - synthesizer should call - // processSpeechQueue to continue running the queue - Log.i("TTS processing: ", currentSpeechItem.mText); - if (sr == null) { - if (currentSpeechItem.mType == SpeechItem.SPEECH) { - // TODO: Split text up into smaller chunks before accepting - // them - // for processing. - speakInternalOnly(currentSpeechItem.mText, - currentSpeechItem.mParams); - } else { - // This is either silence or an earcon that was missing - silence(currentSpeechItem.mDuration); - } - } else { - cleanUpPlayer(); - if (sr.mSourcePackageName == PKGNAME) { - // Utterance is part of the TTS library - player = 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(); - speechQueue.remove(0); // Remove it from the queue and - // move on - isSpeaking = false; - return; - } - player = MediaPlayer.create(ctx, sr.mResId); - } else { - // Utterance is coming from a file - player = 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 (player == null) { - speechQueue.clear(); - isSpeaking = false; - return; + private void setLanguage(String lang) { + if (prefs.getBoolean("override_pref", false)) { + // This is set to the default here so that the preview in the prefs + // activity will show the change without a restart, even if apps are + // not + // allowed to change the defaults. + lang = prefs.getString("lang_pref", "en-rUS"); } - player.setOnCompletionListener(this); - try { - player.start(); - } catch (IllegalStateException e) { - speechQueue.clear(); - isSpeaking = false; - cleanUpPlayer(); - return; - } - } - if (speechQueue.size() > 0) { - speechQueue.remove(0); - } - } 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 void cleanUpPlayer() { - if (player != null) { - player.release(); - player = null; - } - } - - /** - * Synthesizes the given text using the specified queuing mode and - * 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 - */ - private boolean synthesizeToFile(String text, ArrayList<String> params, - String filename, boolean calledFromApi) { - // Only stop everything if this is a call made by an outside app trying - // to - // use the API. Do NOT stop if this is a call from within the service as - // clearing the speech queue here would be a mistake. - if (calledFromApi) { - stop(); - } - Log.i("TTS", "Synthesizing to " + filename); - boolean synthAvailable = false; - try { - synthAvailable = synthesizerLock.tryLock(); - if (!synthAvailable) { - return false; - } - // Don't allow a filename that is too long - // TODO use platform constant - if (filename.length() > 250) { - return false; - } - nativeSynth.synthesizeToFile(text, filename); - } finally { - // This check is needed because finally will always run; even if the - // method returns somewhere in the try block. - if (synthAvailable) { - synthesizerLock.unlock(); - } - } - Log.i("TTS", "Completed synthesis for " + filename); - 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 ITts.Stub mBinder = new Stub() { - - public void registerCallback(ITtsCallback cb) { - if (cb != null) - mCallbacks.register(cb); + nativeSynth.setLanguage(lang); } - public void unregisterCallback(ITtsCallback cb) { - if (cb != null) - mCallbacks.unregister(cb); - } - - /** - * Gives a hint about the type of engine that is preferred. - * - * @param selectedEngine - * The TTS engine that should be used - */ - public void setEngine(String engineName, String[] supportedLanguages, - int strictness) { - self.setEngine(engineName, supportedLanguages, strictness); - } - - /** - * Specifies exactly what the engine has to support. Will always be - * considered "strict"; can be used for implementing - * optional/experimental features that are not supported by all engines. - * - * @param engineIntent - * An intent that specifies exactly what the engine has to - * support. - */ - public void setEngineWithIntent(Intent engineIntent) { - self.setEngine(engineIntent); - } - - /** - * Speaks the given text using the specified queueing mode and - * parameters. - * - * @param text - * The text that should be spoken - * @param queueMode - * 0 for no queue (interrupts all previous utterances), 1 for - * queued - * @param params - * An ArrayList of parameters. The first element of this - * array controls the type of voice to use. - */ - public void speak(String text, int queueMode, String[] params) { - ArrayList<String> speakingParams = new ArrayList<String>(); - if (params != null) { - speakingParams = new ArrayList<String>(Arrays.asList(params)); - } - self.speak(text, queueMode, speakingParams); - } - - /** - * Plays the earcon using the specified queueing mode and parameters. - * - * @param earcon - * The earcon that should be played - * @param queueMode - * 0 for no queue (interrupts all previous utterances), 1 for - * queued - * @param params - * An ArrayList of parameters. - */ - public void playEarcon(String earcon, int queueMode, String[] params) { - ArrayList<String> speakingParams = new ArrayList<String>(); - if (params != null) { - speakingParams = new ArrayList<String>(Arrays.asList(params)); - } - self.playEarcon(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 - * 0 for no queue (interrupts all previous utterances), 1 for - * queued - * @param params - * An ArrayList of parameters. - */ - public void playSilence(long duration, int queueMode, String[] params) { - ArrayList<String> speakingParams = new ArrayList<String>(); - if (params != null) { - speakingParams = new ArrayList<String>(Arrays.asList(params)); - } - self.playSilence(duration, queueMode, speakingParams); + private void setEngine(String engineName, String[] requestedLanguages, + int strictness) { + // TODO: Implement engine selection code here. + Intent engineIntent = new Intent( + "android.intent.action.START_TTS_ENGINE"); + if (engineName != null) { + engineIntent.addCategory("android.intent.action.tts_engine." + + engineName); + } + for (int i = 0; i < requestedLanguages.length; i++) { + engineIntent.addCategory("android.intent.action.tts_lang." + + requestedLanguages[i]); + } + ResolveInfo[] enginesArray = new ResolveInfo[0]; + PackageManager pm = getPackageManager(); + enginesArray = pm.queryIntentActivities(engineIntent, 0).toArray( + enginesArray); } - - /** - * Stops all speech output and removes any utterances still in the - * queue. - */ - public void stop() { - self.stop(); + private void setEngine(Intent engineIntent) { + // TODO: Implement engine selection code here. } - /** - * Returns whether or not the TTS is speaking. - * - * @return Boolean to indicate whether or not the TTS is speaking - */ - public boolean isSpeaking() { - return (self.isSpeaking && (speechQueue.size() < 1)); + private int getEngineStatus() { + // TODO: Proposal - add a sanity check method that + // TTS engine plugins must implement. + return 0; } /** @@ -686,8 +209,8 @@ public class TtsService extends Service implements OnCompletionListener { * @param resId * The resource ID of the sound within its package */ - public void addSpeech(String text, String packageName, int resId) { - self.addSpeech(text, packageName, resId); + private void addSpeech(String text, String packageName, int resId) { + mUtterances.put(text, new SoundResource(packageName, resId)); } /** @@ -696,11 +219,11 @@ public class TtsService extends Service implements OnCompletionListener { * @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). + * The filename of the sound resource. This must be a complete + * path like: (/sdcard/mysounds/mysoundbite.mp3). */ - public void addSpeechFile(String text, String filename) { - self.addSpeech(text, filename); + private void addSpeech(String text, String filename) { + mUtterances.put(text, new SoundResource(filename)); } /** @@ -713,8 +236,8 @@ public class TtsService extends Service implements OnCompletionListener { * @param resId * The resource ID of the sound within its package */ - public void addEarcon(String earcon, String packageName, int resId) { - self.addEarcon(earcon, packageName, resId); + private void addEarcon(String earcon, String packageName, int resId) { + mEarcons.put(earcon, new SoundResource(packageName, resId)); } /** @@ -723,61 +246,538 @@ public class TtsService extends Service implements OnCompletionListener { * @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). + * The filename of the sound resource. This must be a complete + * path like: (/sdcard/mysounds/mysoundbite.mp3). */ - public void addEarconFile(String earcon, String filename) { - self.addEarcon(earcon, filename); + private void addEarcon(String earcon, String filename) { + mEarcons.put(earcon, new SoundResource(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. + * Speaks the given text using the specified queueing mode and parameters. * - * @param speechRate - * The speech rate that should be used + * @param text + * The text that should be spoken + * @param queueMode + * 0 for no queue (interrupts all previous utterances), 1 for + * queued + * @param params + * An ArrayList of parameters. This is not implemented for all + * engines. */ - public void setSpeechRate(int speechRate) { - self.setSpeechRate(speechRate); + private void speak(String text, int queueMode, ArrayList<String> params) { + if (queueMode == 0) { + stop(); + } + mSpeechQueue.add(new SpeechItem(text, params, SpeechItem.SPEECH)); + if (!mIsSpeaking) { + processSpeechQueue(); + } } - // TODO: Fix comment about language /** - * 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. + * Plays the earcon using the specified queueing mode and parameters. * - * @param language - * The language to be used. The languages are specified by - * their IETF language tags as defined by BCP 47. This is the - * same standard used for the lang attribute in HTML. See: - * http://en.wikipedia.org/wiki/IETF_language_tag + * @param earcon + * The earcon that should be played + * @param queueMode + * 0 for no queue (interrupts all previous utterances), 1 for + * queued + * @param params + * An ArrayList of parameters. This is not implemented for all + * engines. */ - public void setLanguage(String language) { - self.setLanguage(language); + private void playEarcon(String earcon, int queueMode, + ArrayList<String> params) { + if (queueMode == 0) { + stop(); + } + mSpeechQueue.add(new SpeechItem(earcon, params, SpeechItem.EARCON)); + if (!mIsSpeaking) { + processSpeechQueue(); + } + } + + /** + * Stops all speech output and removes any utterances still in the queue. + */ + private void stop() { + Log.i("TTS", "Stopping"); + mSpeechQueue.clear(); + + nativeSynth.stop(); + mIsSpeaking = false; + if (mPlayer != null) { + try { + mPlayer.stop(); + } catch (IllegalStateException e) { + // Do nothing, the player is already stopped. + } + } + Log.i("TTS", "Stopped"); + } + + public void onCompletion(MediaPlayer arg0) { + processSpeechQueue(); + } + + private void playSilence(long duration, int queueMode, + ArrayList<String> params) { + if (queueMode == 0) { + stop(); + } + mSpeechQueue.add(new SpeechItem(duration)); + if (!mIsSpeaking) { + processSpeechQueue(); + } + } + + private void silence(final long duration) { + class SilenceThread implements Runnable { + public void run() { + try { + Thread.sleep(duration); + } catch (InterruptedException e) { + e.printStackTrace(); + } finally { + processSpeechQueue(); + } + } + } + Thread slnc = (new Thread(new SilenceThread())); + slnc.setPriority(Thread.MIN_PRIORITY); + slnc.start(); + } + + private void speakInternalOnly(final String text, + final ArrayList<String> params) { + class SynthThread implements Runnable { + public void run() { + boolean synthAvailable = false; + try { + synthAvailable = synthesizerLock.tryLock(); + if (!synthAvailable) { + Thread.sleep(100); + Thread synth = (new Thread(new SynthThread())); + synth.setPriority(Thread.MIN_PRIORITY); + synth.start(); + return; + } + nativeSynth.speak(text); + } catch (InterruptedException e) { + e.printStackTrace(); + } finally { + // This check is needed because finally will always run; + // even if the + // method returns somewhere in the try block. + if (synthAvailable) { + synthesizerLock.unlock(); + } + } + } + } + Thread synth = (new Thread(new SynthThread())); + synth.setPriority(Thread.MIN_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 dispatchSpeechCompletedCallbacks(String mark) { + Log.i("TTS callback", "dispatch started"); + // Broadcast to all clients the new value. + final int N = mCallbacks.beginBroadcast(); + for (int i = 0; i < N; i++) { + try { + mCallbacks.getBroadcastItem(i).markReached(mark); + } catch (RemoteException e) { + // The RemoteCallbackList will take care of removing + // the dead object for us. + } + } + mCallbacks.finishBroadcast(); + Log.i("TTS callback", "dispatch completed to " + N); + } + + private void processSpeechQueue() { + boolean speechQueueAvailable = false; + try { + speechQueueAvailable = speechQueueLock.tryLock(); + if (!speechQueueAvailable) { + return; + } + if (mSpeechQueue.size() < 1) { + mIsSpeaking = false; + // Dispatch a completion here as this is the + // only place where speech completes normally. + // Nothing left to say in the queue is a special case + // that is always a "mark" - associated text is null. + dispatchSpeechCompletedCallbacks(""); + return; + } + + SpeechItem currentSpeechItem = mSpeechQueue.get(0); + mIsSpeaking = true; + SoundResource sr = getSoundResource(currentSpeechItem); + // Synth speech as needed - synthesizer should call + // processSpeechQueue to continue running the queue + Log.i("TTS processing: ", currentSpeechItem.mText); + if (sr == null) { + if (currentSpeechItem.mType == SpeechItem.SPEECH) { + // TODO: Split text up into smaller chunks before accepting + // them + // for processing. + speakInternalOnly(currentSpeechItem.mText, + currentSpeechItem.mParams); + } else { + // This is either silence or an earcon that was missing + silence(currentSpeechItem.mDuration); + } + } 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.start(); + } catch (IllegalStateException e) { + mSpeechQueue.clear(); + mIsSpeaking = false; + cleanUpPlayer(); + return; + } + } + if (mSpeechQueue.size() > 0) { + mSpeechQueue.remove(0); + } + } 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 void cleanUpPlayer() { + if (mPlayer != null) { + mPlayer.release(); + mPlayer = null; + } } /** - * Speaks the given text using the specified queueing mode and + * Synthesizes the given text using the specified queuing mode and * 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. + * 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". + * 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 text, String[] params, - String filename) { - ArrayList<String> speakingParams = new ArrayList<String>(); - if (params != null) { - speakingParams = new ArrayList<String>(Arrays.asList(params)); - } - return self.synthesizeToFile(text, speakingParams, filename, true); + private boolean synthesizeToFile(String text, ArrayList<String> params, + String filename, boolean calledFromApi) { + // Only stop everything if this is a call made by an outside app trying + // to + // use the API. Do NOT stop if this is a call from within the service as + // clearing the speech queue here would be a mistake. + if (calledFromApi) { + stop(); + } + Log.i("TTS", "Synthesizing to " + filename); + boolean synthAvailable = false; + try { + synthAvailable = synthesizerLock.tryLock(); + if (!synthAvailable) { + return false; + } + // Don't allow a filename that is too long + // TODO use platform constant + if (filename.length() > 250) { + return false; + } + nativeSynth.synthesizeToFile(text, filename); + } finally { + // This check is needed because finally will always run; even if the + // method returns somewhere in the try block. + if (synthAvailable) { + synthesizerLock.unlock(); + } + } + Log.i("TTS", "Completed synthesis for " + filename); + 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 ITts.Stub mBinder = new Stub() { + + public void registerCallback(ITtsCallback cb) { + if (cb != null) + mCallbacks.register(cb); + } + + public void unregisterCallback(ITtsCallback cb) { + if (cb != null) + mCallbacks.unregister(cb); + } + + /** + * Gives a hint about the type of engine that is preferred. + * + * @param selectedEngine + * The TTS engine that should be used + */ + public void setEngine(String engineName, String[] supportedLanguages, + int strictness) { + mSelf.setEngine(engineName, supportedLanguages, strictness); + } + + /** + * Specifies exactly what the engine has to support. Will always be + * considered "strict"; can be used for implementing + * optional/experimental features that are not supported by all engines. + * + * @param engineIntent + * An intent that specifies exactly what the engine has to + * support. + */ + public void setEngineWithIntent(Intent engineIntent) { + mSelf.setEngine(engineIntent); + } + + /** + * Speaks the given text using the specified queueing mode and + * parameters. + * + * @param text + * The text that should be spoken + * @param queueMode + * 0 for no queue (interrupts all previous utterances), 1 for + * queued + * @param params + * An ArrayList of parameters. The first element of this + * array controls the type of voice to use. + */ + public void speak(String text, int queueMode, String[] params) { + ArrayList<String> speakingParams = new ArrayList<String>(); + if (params != null) { + speakingParams = new ArrayList<String>(Arrays.asList(params)); + } + mSelf.speak(text, queueMode, speakingParams); + } + + /** + * Plays the earcon using the specified queueing mode and parameters. + * + * @param earcon + * The earcon that should be played + * @param queueMode + * 0 for no queue (interrupts all previous utterances), 1 for + * queued + * @param params + * An ArrayList of parameters. + */ + public void playEarcon(String earcon, int queueMode, String[] params) { + ArrayList<String> speakingParams = new ArrayList<String>(); + if (params != null) { + speakingParams = new ArrayList<String>(Arrays.asList(params)); + } + mSelf.playEarcon(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 + * 0 for no queue (interrupts all previous utterances), 1 for + * queued + * @param params + * An ArrayList of parameters. + */ + public void playSilence(long duration, int queueMode, String[] params) { + ArrayList<String> speakingParams = new ArrayList<String>(); + if (params != null) { + speakingParams = new ArrayList<String>(Arrays.asList(params)); + } + mSelf.playSilence(duration, queueMode, speakingParams); + } + + + /** + * Stops all speech output and removes any utterances still in the + * queue. + */ + public void stop() { + mSelf.stop(); + } + + /** + * 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 text, String packageName, int resId) { + mSelf.addSpeech(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 text, String filename) { + mSelf.addSpeech(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 earcon, String packageName, int resId) { + mSelf.addEarcon(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 earcon, String filename) { + mSelf.addEarcon(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 void setSpeechRate(int speechRate) { + mSelf.setSpeechRate(speechRate); + } + + // TODO: Fix comment about language + /** + * 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 language + * The language to be used. The languages are specified by + * their IETF language tags as defined by BCP 47. This is the + * same standard used for the lang attribute in HTML. See: + * http://en.wikipedia.org/wiki/IETF_language_tag + */ + public void setLanguage(String language) { + mSelf.setLanguage(language); + } + + /** + * Speaks the given text using the specified queueing mode and + * 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 text, String[] params, + String filename) { + ArrayList<String> speakingParams = new ArrayList<String>(); + if (params != null) { + speakingParams = new ArrayList<String>(Arrays.asList(params)); + } + return mSelf.synthesizeToFile(text, speakingParams, filename, true); + } + }; } |