Move Android's TTS platform implementation to content/
Bug: 517317
Change-Id: I9fc1edc58a2386b837b8677c4984ae4e5829c7e0
Reviewed-on: https://chromium-review.googlesource.com/c/1359453
Commit-Queue: Katie Dektar <[email protected]>
Reviewed-by: Dominic Mazzoni <[email protected]>
Reviewed-by: John Abd-El-Malek <[email protected]>
Cr-Commit-Position: refs/heads/master@{#613604}
diff --git a/content/browser/BUILD.gn b/content/browser/BUILD.gn
index 64be0231..d2ac80b 100644
--- a/content/browser/BUILD.gn
+++ b/content/browser/BUILD.gn
@@ -1701,6 +1701,8 @@
"speech/speech_recognizer.h",
"speech/speech_recognizer_impl_android.cc",
"speech/speech_recognizer_impl_android.h",
+ "speech/tts_android.cc",
+ "speech/tts_android.h",
"speech/tts_controller_impl.cc",
"speech/tts_controller_impl.h",
"speech/tts_mac.mm",
diff --git a/content/browser/speech/tts_android.cc b/content/browser/speech/tts_android.cc
new file mode 100644
index 0000000..dce7244
--- /dev/null
+++ b/content/browser/speech/tts_android.cc
@@ -0,0 +1,146 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "content/browser/speech/tts_android.h"
+
+#include <string>
+
+#include "base/android/jni_string.h"
+#include "base/memory/singleton.h"
+#include "base/strings/utf_string_conversions.h"
+#include "content/common/buildflags.h"
+#include "content/public/browser/tts_controller.h"
+#include "jni/TtsPlatformImpl_jni.h"
+
+using base::android::AttachCurrentThread;
+using base::android::JavaParamRef;
+
+namespace content {
+
+TtsPlatformImplAndroid::TtsPlatformImplAndroid() : utterance_id_(0) {
+ JNIEnv* env = AttachCurrentThread();
+ java_ref_.Reset(
+ Java_TtsPlatformImpl_create(env, reinterpret_cast<intptr_t>(this)));
+}
+
+TtsPlatformImplAndroid::~TtsPlatformImplAndroid() {
+ JNIEnv* env = AttachCurrentThread();
+ Java_TtsPlatformImpl_destroy(env, java_ref_);
+}
+
+bool TtsPlatformImplAndroid::PlatformImplAvailable() {
+ return true;
+}
+
+bool TtsPlatformImplAndroid::Speak(
+ int utterance_id,
+ const std::string& utterance,
+ const std::string& lang,
+ const VoiceData& voice,
+ const UtteranceContinuousParameters& params) {
+ JNIEnv* env = AttachCurrentThread();
+ jboolean success = Java_TtsPlatformImpl_speak(
+ env, java_ref_, utterance_id,
+ base::android::ConvertUTF8ToJavaString(env, utterance),
+ base::android::ConvertUTF8ToJavaString(env, lang), params.rate,
+ params.pitch, params.volume);
+ if (!success)
+ return false;
+
+ utterance_ = utterance;
+ utterance_id_ = utterance_id;
+ return true;
+}
+
+bool TtsPlatformImplAndroid::StopSpeaking() {
+ JNIEnv* env = AttachCurrentThread();
+ Java_TtsPlatformImpl_stop(env, java_ref_);
+ utterance_id_ = 0;
+ utterance_.clear();
+ return true;
+}
+
+void TtsPlatformImplAndroid::Pause() {
+ StopSpeaking();
+}
+
+void TtsPlatformImplAndroid::Resume() {}
+
+bool TtsPlatformImplAndroid::IsSpeaking() {
+ return (utterance_id_ != 0);
+}
+
+void TtsPlatformImplAndroid::GetVoices(std::vector<VoiceData>* out_voices) {
+ JNIEnv* env = AttachCurrentThread();
+ if (!Java_TtsPlatformImpl_isInitialized(env, java_ref_))
+ return;
+
+ int count = Java_TtsPlatformImpl_getVoiceCount(env, java_ref_);
+ for (int i = 0; i < count; ++i) {
+ out_voices->push_back(VoiceData());
+ VoiceData& data = out_voices->back();
+ data.native = true;
+ data.name = base::android::ConvertJavaStringToUTF8(
+ Java_TtsPlatformImpl_getVoiceName(env, java_ref_, i));
+ data.lang = base::android::ConvertJavaStringToUTF8(
+ Java_TtsPlatformImpl_getVoiceLanguage(env, java_ref_, i));
+ data.events.insert(TTS_EVENT_START);
+ data.events.insert(TTS_EVENT_END);
+ data.events.insert(TTS_EVENT_ERROR);
+ }
+}
+
+void TtsPlatformImplAndroid::VoicesChanged(JNIEnv* env,
+ const JavaParamRef<jobject>& obj) {
+ TtsController::GetInstance()->VoicesChanged();
+}
+
+void TtsPlatformImplAndroid::OnEndEvent(JNIEnv* env,
+ const JavaParamRef<jobject>& obj,
+ jint utterance_id) {
+ SendFinalTtsEvent(utterance_id, TTS_EVENT_END,
+ static_cast<int>(utterance_.size()));
+}
+
+void TtsPlatformImplAndroid::OnErrorEvent(JNIEnv* env,
+ const JavaParamRef<jobject>& obj,
+ jint utterance_id) {
+ SendFinalTtsEvent(utterance_id, TTS_EVENT_ERROR, 0);
+}
+
+void TtsPlatformImplAndroid::OnStartEvent(JNIEnv* env,
+ const JavaParamRef<jobject>& obj,
+ jint utterance_id) {
+ if (utterance_id != utterance_id_)
+ return;
+
+ TtsController::GetInstance()->OnTtsEvent(utterance_id_, TTS_EVENT_START, 0,
+ std::string());
+}
+
+void TtsPlatformImplAndroid::SendFinalTtsEvent(int utterance_id,
+ TtsEventType event_type,
+ int char_index) {
+ if (utterance_id != utterance_id_)
+ return;
+
+ TtsController::GetInstance()->OnTtsEvent(utterance_id_, event_type,
+ char_index, std::string());
+ utterance_id_ = 0;
+ utterance_.clear();
+}
+
+// static
+TtsPlatformImplAndroid* TtsPlatformImplAndroid::GetInstance() {
+ return base::Singleton<
+ TtsPlatformImplAndroid,
+ base::LeakySingletonTraits<TtsPlatformImplAndroid>>::get();
+}
+
+// static
+TtsPlatformImpl* TtsPlatformImpl::GetInstance() {
+ return TtsPlatformImplAndroid::GetInstance();
+}
+
+} // namespace content
diff --git a/content/browser/speech/tts_android.h b/content/browser/speech/tts_android.h
new file mode 100644
index 0000000..da8dd04
--- /dev/null
+++ b/content/browser/speech/tts_android.h
@@ -0,0 +1,64 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CONTENT_BROWSER_SPEECH_TTS_ANDROID_H_
+#define CONTENT_BROWSER_SPEECH_TTS_ANDROID_H_
+
+#include "base/android/scoped_java_ref.h"
+#include "base/macros.h"
+#include "content/browser/speech/tts_platform_impl.h"
+
+namespace content {
+
+class TtsPlatformImplAndroid : public TtsPlatformImpl {
+ public:
+ // TtsPlatform overrides.
+ bool PlatformImplAvailable() override;
+ bool Speak(int utterance_id,
+ const std::string& utterance,
+ const std::string& lang,
+ const VoiceData& voice,
+ const UtteranceContinuousParameters& params) override;
+ bool StopSpeaking() override;
+ void Pause() override;
+ void Resume() override;
+ bool IsSpeaking() override;
+ void GetVoices(std::vector<VoiceData>* out_voices) override;
+
+ // Methods called from Java via JNI.
+ void VoicesChanged(JNIEnv* env,
+ const base::android::JavaParamRef<jobject>& obj);
+ void OnEndEvent(JNIEnv* env,
+ const base::android::JavaParamRef<jobject>& obj,
+ jint utterance_id);
+ void OnErrorEvent(JNIEnv* env,
+ const base::android::JavaParamRef<jobject>& obj,
+ jint utterance_id);
+ void OnStartEvent(JNIEnv* env,
+ const base::android::JavaParamRef<jobject>& obj,
+ jint utterance_id);
+
+ // Static functions.
+ static TtsPlatformImplAndroid* GetInstance();
+
+ private:
+ friend struct base::DefaultSingletonTraits<TtsPlatformImplAndroid>;
+
+ TtsPlatformImplAndroid();
+ ~TtsPlatformImplAndroid() override;
+
+ void SendFinalTtsEvent(int utterance_id,
+ TtsEventType event_type,
+ int char_index);
+
+ base::android::ScopedJavaGlobalRef<jobject> java_ref_;
+ int utterance_id_;
+ std::string utterance_;
+
+ DISALLOW_COPY_AND_ASSIGN(TtsPlatformImplAndroid);
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_SPEECH_TTS_ANDROID_H_
diff --git a/content/browser/speech/tts_platform_impl.cc b/content/browser/speech/tts_platform_impl.cc
index 77461545..0a9a1bd 100644
--- a/content/browser/speech/tts_platform_impl.cc
+++ b/content/browser/speech/tts_platform_impl.cc
@@ -13,8 +13,8 @@
// static
TtsPlatform* TtsPlatform::GetInstance() {
-#if defined(OS_CHROMEOS) || defined(OS_ANDROID)
- // Chrome and Android TTS platforms have chrome/ dependencies.
+#if defined(OS_CHROMEOS)
+ // Chrome TTS platform has chrome/ dependencies.
return GetContentClient()->browser()->GetTtsPlatform();
#elif defined(OS_FUCHSIA)
// There is no platform TTS definition for Fuchsia.
@@ -44,4 +44,4 @@
error_ = error;
}
-} // namespace content
\ No newline at end of file
+} // namespace content
diff --git a/content/public/android/BUILD.gn b/content/public/android/BUILD.gn
index 98da942..5d6b10a3 100644
--- a/content/public/android/BUILD.gn
+++ b/content/public/android/BUILD.gn
@@ -135,10 +135,12 @@
"java/src/org/chromium/content/browser/JavascriptInterface.java",
"java/src/org/chromium/content/browser/JoystickHandler.java",
"java/src/org/chromium/content/browser/LauncherThread.java",
+ "java/src/org/chromium/content/browser/LollipopTtsPlatformImpl.java",
"java/src/org/chromium/content/browser/MediaSessionImpl.java",
"java/src/org/chromium/content/browser/MemoryMonitorAndroid.java",
"java/src/org/chromium/content/browser/MotionEventSynthesizerImpl.java",
"java/src/org/chromium/content/browser/NfcHost.java",
+ "java/src/org/chromium/content/browser/TtsPlatformImpl.java",
"java/src/org/chromium/content/browser/PopupController.java",
"java/src/org/chromium/content/browser/RenderCoordinatesImpl.java",
"java/src/org/chromium/content/browser/ScreenOrientationProviderImpl.java",
@@ -383,6 +385,7 @@
"java/src/org/chromium/content/browser/SpeechRecognitionImpl.java",
"java/src/org/chromium/content/browser/SyntheticGestureTarget.java",
"java/src/org/chromium/content/browser/TracingControllerAndroidImpl.java",
+ "java/src/org/chromium/content/browser/TtsPlatformImpl.java",
"java/src/org/chromium/content/browser/accessibility/BrowserAccessibilityState.java",
"java/src/org/chromium/content/browser/accessibility/WebContentsAccessibilityImpl.java",
"java/src/org/chromium/content/browser/accessibility/captioning/CaptioningController.java",
diff --git a/content/public/android/java/src/org/chromium/content/browser/LollipopTtsPlatformImpl.java b/content/public/android/java/src/org/chromium/content/browser/LollipopTtsPlatformImpl.java
new file mode 100644
index 0000000..6e4fc58
--- /dev/null
+++ b/content/public/android/java/src/org/chromium/content/browser/LollipopTtsPlatformImpl.java
@@ -0,0 +1,64 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.content.browser;
+
+import android.annotation.TargetApi;
+import android.os.Build;
+import android.os.Bundle;
+import android.speech.tts.TextToSpeech;
+import android.speech.tts.UtteranceProgressListener;
+
+import org.chromium.base.annotations.JNINamespace;
+
+/**
+ * Subclass of TtsPlatformImpl for Lollipop to make use of newer APIs.
+ */
+@JNINamespace("content")
+@TargetApi(Build.VERSION_CODES.LOLLIPOP)
+class LollipopTtsPlatformImpl extends TtsPlatformImpl {
+ protected LollipopTtsPlatformImpl(long nativeTtsPlatformImplAndroid) {
+ super(nativeTtsPlatformImplAndroid);
+ }
+
+ /**
+ * Overrides TtsPlatformImpl because the API changed in Lollipop.
+ */
+ @Override
+ protected void addOnUtteranceProgressListener() {
+ mTextToSpeech.setOnUtteranceProgressListener(new UtteranceProgressListener() {
+ @Override
+ public void onDone(final String utteranceId) {
+ sendEndEventOnUiThread(utteranceId);
+ }
+
+ @Override
+ public void onError(final String utteranceId, int errorCode) {
+ sendErrorEventOnUiThread(utteranceId);
+ }
+
+ @Override
+ @Deprecated
+ public void onError(final String utteranceId) {}
+
+ @Override
+ public void onStart(final String utteranceId) {
+ sendStartEventOnUiThread(utteranceId);
+ }
+ });
+ }
+
+ /**
+ * Overrides TtsPlatformImpl because the API changed in Lollipop.
+ */
+ @Override
+ protected int callSpeak(String text, float volume, int utteranceId) {
+ Bundle params = new Bundle();
+ if (volume != 1.0) {
+ params.putFloat(TextToSpeech.Engine.KEY_PARAM_VOLUME, volume);
+ }
+ return mTextToSpeech.speak(
+ text, TextToSpeech.QUEUE_FLUSH, params, Integer.toString(utteranceId));
+ }
+}
diff --git a/content/public/android/java/src/org/chromium/content/browser/TtsPlatformImpl.java b/content/public/android/java/src/org/chromium/content/browser/TtsPlatformImpl.java
new file mode 100644
index 0000000..d7bf9d5
--- /dev/null
+++ b/content/public/android/java/src/org/chromium/content/browser/TtsPlatformImpl.java
@@ -0,0 +1,319 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.content.browser;
+
+import android.os.Build;
+import android.speech.tts.TextToSpeech;
+import android.speech.tts.UtteranceProgressListener;
+
+import org.chromium.base.ContextUtils;
+import org.chromium.base.ThreadUtils;
+import org.chromium.base.TraceEvent;
+import org.chromium.base.annotations.CalledByNative;
+import org.chromium.base.annotations.JNINamespace;
+import org.chromium.base.task.AsyncTask;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+
+/**
+ * This class is the Java counterpart to the C++ TtsPlatformImplAndroid class.
+ * It implements the Android-native text-to-speech code to support the web
+ * speech synthesis API.
+ *
+ * Threading model note: all calls from C++ must happen on the UI thread.
+ * Callbacks from Android may happen on a different thread, so we always
+ * use ThreadUtils.runOnUiThread when calling back to C++.
+ */
+@JNINamespace("content")
+class TtsPlatformImpl {
+ private static class TtsVoice {
+ private TtsVoice(String name, String language) {
+ mName = name;
+ mLanguage = language;
+ }
+ private final String mName;
+ private final String mLanguage;
+ }
+
+ private static class PendingUtterance {
+ private PendingUtterance(TtsPlatformImpl impl, int utteranceId, String text, String lang,
+ float rate, float pitch, float volume) {
+ mImpl = impl;
+ mUtteranceId = utteranceId;
+ mText = text;
+ mLang = lang;
+ mRate = rate;
+ mPitch = pitch;
+ mVolume = volume;
+ }
+
+ private void speak() {
+ mImpl.speak(mUtteranceId, mText, mLang, mRate, mPitch, mVolume);
+ }
+
+ TtsPlatformImpl mImpl;
+ int mUtteranceId;
+ String mText;
+ String mLang;
+ float mRate;
+ float mPitch;
+ float mVolume;
+ }
+
+ private long mNativeTtsPlatformImplAndroid;
+ protected final TextToSpeech mTextToSpeech;
+ private boolean mInitialized;
+ private List<TtsVoice> mVoices;
+ private String mCurrentLanguage;
+ private PendingUtterance mPendingUtterance;
+
+ protected TtsPlatformImpl(long nativeTtsPlatformImplAndroid) {
+ mInitialized = false;
+ mNativeTtsPlatformImplAndroid = nativeTtsPlatformImplAndroid;
+ mTextToSpeech = new TextToSpeech(ContextUtils.getApplicationContext(), status -> {
+ if (status == TextToSpeech.SUCCESS) {
+ ThreadUtils.runOnUiThread(() -> initialize());
+ }
+ });
+ addOnUtteranceProgressListener();
+ }
+
+ /**
+ * Create a TtsPlatformImpl object, which is owned by TtsPlatformImplAndroid
+ * on the C++ side.
+ * @param nativeTtsPlatformImplAndroid The C++ object that owns us.
+ *
+ */
+ @CalledByNative
+ private static TtsPlatformImpl create(long nativeTtsPlatformImplAndroid) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ return new LollipopTtsPlatformImpl(nativeTtsPlatformImplAndroid);
+ } else {
+ return new TtsPlatformImpl(nativeTtsPlatformImplAndroid);
+ }
+ }
+
+ /**
+ * Called when our C++ counterpoint is deleted. Clear the handle to our
+ * native C++ object, ensuring it's never called.
+ */
+ @CalledByNative
+ private void destroy() {
+ mNativeTtsPlatformImplAndroid = 0;
+ }
+
+ /**
+ * @return true if our TextToSpeech object is initialized and we've
+ * finished scanning the list of voices.
+ */
+ @CalledByNative
+ private boolean isInitialized() {
+ return mInitialized;
+ }
+
+ /**
+ * @return the number of voices.
+ */
+ @CalledByNative
+ private int getVoiceCount() {
+ assert mInitialized;
+ return mVoices.size();
+ }
+
+ /**
+ * @return the name of the voice at a given index.
+ */
+ @CalledByNative
+ private String getVoiceName(int voiceIndex) {
+ assert mInitialized;
+ return mVoices.get(voiceIndex).mName;
+ }
+
+ /**
+ * @return the language of the voice at a given index.
+ */
+ @CalledByNative
+ private String getVoiceLanguage(int voiceIndex) {
+ assert mInitialized;
+ return mVoices.get(voiceIndex).mLanguage;
+ }
+
+ /**
+ * Attempt to start speaking an utterance. If it returns true, will call back on
+ * start and end.
+ *
+ * @param utteranceId A unique id for this utterance so that callbacks can be tied
+ * to a particular utterance.
+ * @param text The text to speak.
+ * @param lang The language code for the text (e.g., "en-US").
+ * @param rate The speech rate, in the units expected by Android TextToSpeech.
+ * @param pitch The speech pitch, in the units expected by Android TextToSpeech.
+ * @param volume The speech volume, in the units expected by Android TextToSpeech.
+ * @return true on success.
+ */
+ @CalledByNative
+ private boolean speak(
+ int utteranceId, String text, String lang, float rate, float pitch, float volume) {
+ if (!mInitialized) {
+ mPendingUtterance =
+ new PendingUtterance(this, utteranceId, text, lang, rate, pitch, volume);
+ return true;
+ }
+ if (mPendingUtterance != null) mPendingUtterance = null;
+
+ if (!lang.equals(mCurrentLanguage)) {
+ mTextToSpeech.setLanguage(new Locale(lang));
+ mCurrentLanguage = lang;
+ }
+
+ mTextToSpeech.setSpeechRate(rate);
+ mTextToSpeech.setPitch(pitch);
+
+ int result = callSpeak(text, volume, utteranceId);
+ return (result == TextToSpeech.SUCCESS);
+ }
+
+ /**
+ * Stop the current utterance.
+ */
+ @CalledByNative
+ private void stop() {
+ if (mInitialized) mTextToSpeech.stop();
+ if (mPendingUtterance != null) mPendingUtterance = null;
+ }
+
+ /**
+ * Post a task to the UI thread to send the TTS "end" event.
+ */
+ protected void sendEndEventOnUiThread(final String utteranceId) {
+ ThreadUtils.runOnUiThread(() -> {
+ if (mNativeTtsPlatformImplAndroid != 0) {
+ nativeOnEndEvent(mNativeTtsPlatformImplAndroid, Integer.parseInt(utteranceId));
+ }
+ });
+ }
+
+ /**
+ * Post a task to the UI thread to send the TTS "error" event.
+ */
+ protected void sendErrorEventOnUiThread(final String utteranceId) {
+ ThreadUtils.runOnUiThread(() -> {
+ if (mNativeTtsPlatformImplAndroid != 0) {
+ nativeOnErrorEvent(mNativeTtsPlatformImplAndroid, Integer.parseInt(utteranceId));
+ }
+ });
+ }
+
+ /**
+ * Post a task to the UI thread to send the TTS "start" event.
+ */
+ protected void sendStartEventOnUiThread(final String utteranceId) {
+ ThreadUtils.runOnUiThread(() -> {
+ if (mNativeTtsPlatformImplAndroid != 0) {
+ nativeOnStartEvent(mNativeTtsPlatformImplAndroid, Integer.parseInt(utteranceId));
+ }
+ });
+ }
+
+ /**
+ * This is overridden by LollipopTtsPlatformImpl because the API changed.
+ */
+ @SuppressWarnings("deprecation")
+ protected void addOnUtteranceProgressListener() {
+ mTextToSpeech.setOnUtteranceProgressListener(new UtteranceProgressListener() {
+ @Override
+ public void onDone(final String utteranceId) {
+ sendEndEventOnUiThread(utteranceId);
+ }
+
+ // This is deprecated in Lollipop and higher but we still need to catch it
+ // on pre-Lollipop builds.
+ @Override
+ @SuppressWarnings("deprecation")
+ public void onError(final String utteranceId) {
+ sendErrorEventOnUiThread(utteranceId);
+ }
+
+ @Override
+ public void onStart(final String utteranceId) {
+ sendStartEventOnUiThread(utteranceId);
+ }
+ });
+ }
+
+ /**
+ * This is overridden by LollipopTtsPlatformImpl because the API changed.
+ */
+ @SuppressWarnings("deprecation")
+ protected int callSpeak(String text, float volume, int utteranceId) {
+ HashMap<String, String> params = new HashMap<String, String>();
+ if (volume != 1.0) {
+ params.put(TextToSpeech.Engine.KEY_PARAM_VOLUME, Double.toString(volume));
+ }
+ params.put(TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID, Integer.toString(utteranceId));
+ return mTextToSpeech.speak(text, TextToSpeech.QUEUE_FLUSH, params);
+ }
+
+ /**
+ * Note: we enforce that this method is called on the UI thread, so
+ * we can call nativeVoicesChanged directly.
+ */
+ private void initialize() {
+ TraceEvent.begin("TtsPlatformImpl:initialize");
+
+ new AsyncTask<List<TtsVoice>>() {
+ @Override
+ protected List<TtsVoice> doInBackground() {
+ assert mNativeTtsPlatformImplAndroid != 0;
+
+ try (TraceEvent te = TraceEvent.scoped("TtsPlatformImpl:initialize.async_task")) {
+ Locale[] locales = Locale.getAvailableLocales();
+ final List<TtsVoice> voices = new ArrayList<>();
+ for (Locale locale : locales) {
+ if (!locale.getVariant().isEmpty()) continue;
+ try {
+ if (mTextToSpeech.isLanguageAvailable(locale) > 0) {
+ String name = locale.getDisplayLanguage();
+ if (!locale.getCountry().isEmpty()) {
+ name += " " + locale.getDisplayCountry();
+ }
+ TtsVoice voice = new TtsVoice(name, locale.toString());
+ voices.add(voice);
+ }
+ } catch (Exception e) {
+ // Just skip the locale if it's invalid.
+ //
+ // We used to catch only java.util.MissingResourceException,
+ // but we need to catch more exceptions to work around a bug
+ // in Google TTS when we query "bn".
+ // http://crbug.com/792856
+ }
+ }
+ return voices;
+ }
+ }
+
+ @Override
+ protected void onPostExecute(List<TtsVoice> voices) {
+ mVoices = voices;
+ mInitialized = true;
+
+ nativeVoicesChanged(mNativeTtsPlatformImplAndroid);
+
+ if (mPendingUtterance != null) mPendingUtterance.speak();
+
+ TraceEvent.end("TtsPlatformImpl:initialize");
+ }
+ }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
+ }
+
+ private native void nativeVoicesChanged(long nativeTtsPlatformImplAndroid);
+ private native void nativeOnEndEvent(long nativeTtsPlatformImplAndroid, int utteranceId);
+ private native void nativeOnStartEvent(long nativeTtsPlatformImplAndroid, int utteranceId);
+ private native void nativeOnErrorEvent(long nativeTtsPlatformImplAndroid, int utteranceId);
+}