1 // Copyright (c) 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 package org.chromium.chrome.browser;
7 import android.content.Context;
8 import android.speech.tts.TextToSpeech;
9 import android.speech.tts.UtteranceProgressListener;
10 import java.lang.Double;
11 import java.lang.Integer;
12 import java.util.ArrayList;
13 import java.util.HashMap;
14 import java.util.Locale;
15 import org.chromium.base.CalledByNative;
16 import org.chromium.base.ThreadUtils;
19 * This class is the Java counterpart to the C++ TtsPlatformImplAndroid class.
20 * It implements the Android-native text-to-speech code to support the web
21 * speech synthesis API.
23 * Threading model note: all calls from C++ must happen on the UI thread.
24 * Callbacks from Android may happen on a different thread, so we always
25 * use ThreadUtils.runOnUiThread when calling back to C++.
27 class TtsPlatformImpl {
28 private static class TtsVoice {
29 private TtsVoice(String name, String language) {
33 private final String mName;
34 private final String mLanguage;
37 private int mNativeTtsPlatformImplAndroid;
38 private final TextToSpeech mTextToSpeech;
39 private boolean mInitialized;
40 private ArrayList<TtsVoice> mVoices;
41 private String mCurrentLanguage;
43 private TtsPlatformImpl(int nativeTtsPlatformImplAndroid, Context context) {
45 mNativeTtsPlatformImplAndroid = nativeTtsPlatformImplAndroid;
46 mTextToSpeech = new TextToSpeech(context, new TextToSpeech.OnInitListener() {
47 public void onInit(int status) {
48 if (status == TextToSpeech.SUCCESS) {
49 ThreadUtils.runOnUiThread(new Runnable() {
58 mTextToSpeech.setOnUtteranceProgressListener(new UtteranceProgressListener() {
59 public void onDone(final String utteranceId) {
60 ThreadUtils.runOnUiThread(new Runnable() {
63 if (mNativeTtsPlatformImplAndroid != 0) {
64 nativeOnEndEvent(mNativeTtsPlatformImplAndroid,
65 Integer.parseInt(utteranceId));
71 public void onError(final String utteranceId) {
72 ThreadUtils.runOnUiThread(new Runnable() {
75 if (mNativeTtsPlatformImplAndroid != 0) {
76 nativeOnErrorEvent(mNativeTtsPlatformImplAndroid,
77 Integer.parseInt(utteranceId));
83 public void onStart(final String utteranceId) {
84 ThreadUtils.runOnUiThread(new Runnable() {
87 if (mNativeTtsPlatformImplAndroid != 0) {
88 nativeOnStartEvent(mNativeTtsPlatformImplAndroid,
89 Integer.parseInt(utteranceId));
98 * Create a TtsPlatformImpl object, which is owned by TtsPlatformImplAndroid
101 * @param nativeTtsPlatformImplAndroid The C++ object that owns us.
102 * @param context The app context.
105 private static TtsPlatformImpl create(int nativeTtsPlatformImplAndroid,
107 return new TtsPlatformImpl(nativeTtsPlatformImplAndroid, context);
111 * Called when our C++ counterpoint is deleted. Clear the handle to our
112 * native C++ object, ensuring it's never called.
115 private void destroy() {
116 mNativeTtsPlatformImplAndroid = 0;
120 * @return true if our TextToSpeech object is initialized and we've
121 * finished scanning the list of voices.
124 private boolean isInitialized() {
129 * @return the number of voices.
132 private int getVoiceCount() {
133 assert mInitialized == true;
134 return mVoices.size();
138 * @return the name of the voice at a given index.
141 private String getVoiceName(int voiceIndex) {
142 assert mInitialized == true;
143 return mVoices.get(voiceIndex).mName;
147 * @return the language of the voice at a given index.
150 private String getVoiceLanguage(int voiceIndex) {
151 assert mInitialized == true;
152 return mVoices.get(voiceIndex).mLanguage;
156 * Attempt to start speaking an utterance. If it returns true, will call back on
159 * @param utteranceId A unique id for this utterance so that callbacks can be tied
160 * to a particular utterance.
161 * @param text The text to speak.
162 * @param lang The language code for the text (e.g., "en-US").
163 * @param rate The speech rate, in the units expected by Android TextToSpeech.
164 * @param pitch The speech pitch, in the units expected by Android TextToSpeech.
165 * @param volume The speech volume, in the units expected by Android TextToSpeech.
166 * @return true on success.
169 private boolean speak(int utteranceId, String text, String lang,
170 float rate, float pitch, float volume) {
171 assert mInitialized == true;
172 if (!lang.equals(mCurrentLanguage)) {
173 mTextToSpeech.setLanguage(new Locale(lang));
174 mCurrentLanguage = lang;
177 mTextToSpeech.setSpeechRate(rate);
178 mTextToSpeech.setPitch(pitch);
179 HashMap<String, String> params = new HashMap<String, String>();
181 params.put(TextToSpeech.Engine.KEY_PARAM_VOLUME, Double.toString(volume));
183 params.put(TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID, Integer.toString(utteranceId));
184 int result = mTextToSpeech.speak(text, TextToSpeech.QUEUE_FLUSH, params);
185 return (result == TextToSpeech.SUCCESS);
189 * Stop the current utterance.
192 private void stop() {
193 assert mInitialized == true;
194 mTextToSpeech.stop();
198 * Note: we enforce that this method is called on the UI thread, so
199 * we can call nativeVoicesChanged directly.
201 private void initialize() {
202 assert mNativeTtsPlatformImplAndroid != 0;
204 // Note: Android supports multiple speech engines, but querying the
205 // metadata about all of them is expensive. So we deliberately only
206 // support the default speech engine, and expose the different
207 // supported languages for the default engine as different voices.
208 String defaultEngineName = mTextToSpeech.getDefaultEngine();
209 String engineLabel = defaultEngineName;
210 for (TextToSpeech.EngineInfo info : mTextToSpeech.getEngines()) {
211 if (info.name.equals(defaultEngineName)) engineLabel = info.label;
213 Locale[] locales = Locale.getAvailableLocales();
214 mVoices = new ArrayList<TtsVoice>();
215 for (int i = 0; i < locales.length; ++i) {
216 if (!locales[i].getVariant().isEmpty()) continue;
217 if (mTextToSpeech.isLanguageAvailable(locales[i]) > 0) {
218 String name = locales[i].getDisplayLanguage();
219 if (!locales[i].getCountry().isEmpty()) {
220 name += " " + locales[i].getDisplayCountry();
222 TtsVoice voice = new TtsVoice(name, locales[i].toString());
228 nativeVoicesChanged(mNativeTtsPlatformImplAndroid);
231 private native void nativeVoicesChanged(int nativeTtsPlatformImplAndroid);
232 private native void nativeOnEndEvent(int nativeTtsPlatformImplAndroid, int utteranceId);
233 private native void nativeOnStartEvent(int nativeTtsPlatformImplAndroid, int utteranceId);
234 private native void nativeOnErrorEvent(int nativeTtsPlatformImplAndroid, int utteranceId);