Upstream version 9.37.195.0
[platform/framework/web/crosswalk.git] / src / chrome / android / java / src / org / chromium / chrome / browser / TtsPlatformImpl.java
1 // Copyright 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.
4
5 package org.chromium.chrome.browser;
6
7 import android.content.Context;
8 import android.speech.tts.TextToSpeech;
9 import android.speech.tts.UtteranceProgressListener;
10
11 import org.chromium.base.CalledByNative;
12 import org.chromium.base.ThreadUtils;
13
14 import java.util.ArrayList;
15 import java.util.HashMap;
16 import java.util.Locale;
17
18 /**
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.
22  *
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++.
26  */
27 class TtsPlatformImpl {
28     private static class TtsVoice {
29         private TtsVoice(String name, String language) {
30             mName = name;
31             mLanguage = language;
32         }
33         private final String mName;
34         private final String mLanguage;
35     }
36
37     private static class PendingUtterance {
38         private PendingUtterance(TtsPlatformImpl impl, int utteranceId, String text,
39                 String lang, float rate, float pitch, float volume) {
40             mImpl = impl;
41             mUtteranceId = utteranceId;
42             mText = text;
43             mLang = lang;
44             mRate = rate;
45             mPitch = pitch;
46             mVolume = volume;
47         }
48
49         private void speak() {
50             mImpl.speak(mUtteranceId, mText, mLang, mRate, mPitch, mVolume);
51         }
52
53         TtsPlatformImpl mImpl;
54         int mUtteranceId;
55         String mText;
56         String mLang;
57         float mRate;
58         float mPitch;
59         float mVolume;
60     }
61
62     private long mNativeTtsPlatformImplAndroid;
63     private final TextToSpeech mTextToSpeech;
64     private boolean mInitialized;
65     private ArrayList<TtsVoice> mVoices;
66     private String mCurrentLanguage;
67     private PendingUtterance mPendingUtterance;
68
69     private TtsPlatformImpl(long nativeTtsPlatformImplAndroid, Context context) {
70         mInitialized = false;
71         mNativeTtsPlatformImplAndroid = nativeTtsPlatformImplAndroid;
72         mTextToSpeech = new TextToSpeech(context, new TextToSpeech.OnInitListener() {
73                 @Override
74                 public void onInit(int status) {
75                     if (status == TextToSpeech.SUCCESS) {
76                         ThreadUtils.runOnUiThread(new Runnable() {
77                             @Override
78                             public void run() {
79                                 initialize();
80                             }
81                         });
82                     }
83                 }
84             });
85         mTextToSpeech.setOnUtteranceProgressListener(new UtteranceProgressListener() {
86                 @Override
87                 public void onDone(final String utteranceId) {
88                     ThreadUtils.runOnUiThread(new Runnable() {
89                         @Override
90                         public void run() {
91                             if (mNativeTtsPlatformImplAndroid != 0) {
92                                 nativeOnEndEvent(mNativeTtsPlatformImplAndroid,
93                                                  Integer.parseInt(utteranceId));
94                             }
95                         }
96                     });
97                 }
98
99                 @Override
100                 public void onError(final String utteranceId) {
101                     ThreadUtils.runOnUiThread(new Runnable() {
102                         @Override
103                         public void run() {
104                             if (mNativeTtsPlatformImplAndroid != 0) {
105                                 nativeOnErrorEvent(mNativeTtsPlatformImplAndroid,
106                                                    Integer.parseInt(utteranceId));
107                             }
108                         }
109                     });
110                 }
111
112                 @Override
113                 public void onStart(final String utteranceId) {
114                     ThreadUtils.runOnUiThread(new Runnable() {
115                         @Override
116                         public void run() {
117                             if (mNativeTtsPlatformImplAndroid != 0) {
118                                 nativeOnStartEvent(mNativeTtsPlatformImplAndroid,
119                                                    Integer.parseInt(utteranceId));
120                             }
121                         }
122                     });
123                 }
124             });
125     }
126
127     /**
128      * Create a TtsPlatformImpl object, which is owned by TtsPlatformImplAndroid
129      * on the C++ side.
130      *
131      * @param nativeTtsPlatformImplAndroid The C++ object that owns us.
132      * @param context The app context.
133      */
134     @CalledByNative
135     private static TtsPlatformImpl create(long nativeTtsPlatformImplAndroid,
136                                           Context context) {
137         return new TtsPlatformImpl(nativeTtsPlatformImplAndroid, context);
138     }
139
140     /**
141      * Called when our C++ counterpoint is deleted. Clear the handle to our
142      * native C++ object, ensuring it's never called.
143      */
144     @CalledByNative
145     private void destroy() {
146         mNativeTtsPlatformImplAndroid = 0;
147     }
148
149     /**
150      * @return true if our TextToSpeech object is initialized and we've
151      * finished scanning the list of voices.
152      */
153     @CalledByNative
154     private boolean isInitialized() {
155         return mInitialized;
156     }
157
158     /**
159      * @return the number of voices.
160      */
161     @CalledByNative
162     private int getVoiceCount() {
163         assert mInitialized == true;
164         return mVoices.size();
165     }
166
167     /**
168      * @return the name of the voice at a given index.
169      */
170     @CalledByNative
171     private String getVoiceName(int voiceIndex) {
172         assert mInitialized == true;
173         return mVoices.get(voiceIndex).mName;
174     }
175
176     /**
177      * @return the language of the voice at a given index.
178      */
179     @CalledByNative
180     private String getVoiceLanguage(int voiceIndex) {
181         assert mInitialized == true;
182         return mVoices.get(voiceIndex).mLanguage;
183     }
184
185     /**
186      * Attempt to start speaking an utterance. If it returns true, will call back on
187      * start and end.
188      *
189      * @param utteranceId A unique id for this utterance so that callbacks can be tied
190      *     to a particular utterance.
191      * @param text The text to speak.
192      * @param lang The language code for the text (e.g., "en-US").
193      * @param rate The speech rate, in the units expected by Android TextToSpeech.
194      * @param pitch The speech pitch, in the units expected by Android TextToSpeech.
195      * @param volume The speech volume, in the units expected by Android TextToSpeech.
196      * @return true on success.
197      */
198     @CalledByNative
199     private boolean speak(int utteranceId, String text, String lang,
200                           float rate, float pitch, float volume) {
201         if (!mInitialized) {
202             mPendingUtterance = new PendingUtterance(this, utteranceId, text, lang, rate,
203                     pitch, volume);
204             return true;
205         }
206         if (mPendingUtterance != null) mPendingUtterance = null;
207
208         if (!lang.equals(mCurrentLanguage)) {
209             mTextToSpeech.setLanguage(new Locale(lang));
210             mCurrentLanguage = lang;
211         }
212
213         mTextToSpeech.setSpeechRate(rate);
214         mTextToSpeech.setPitch(pitch);
215         HashMap<String, String> params = new HashMap<String, String>();
216         if (volume != 1.0) {
217             params.put(TextToSpeech.Engine.KEY_PARAM_VOLUME, Double.toString(volume));
218         }
219         params.put(TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID, Integer.toString(utteranceId));
220         int result = mTextToSpeech.speak(text, TextToSpeech.QUEUE_FLUSH, params);
221         return (result == TextToSpeech.SUCCESS);
222     }
223
224     /**
225      * Stop the current utterance.
226      */
227     @CalledByNative
228     private void stop() {
229         if (mInitialized) mTextToSpeech.stop();
230         if (mPendingUtterance != null) mPendingUtterance = null;
231     }
232
233     /**
234      * Note: we enforce that this method is called on the UI thread, so
235      * we can call nativeVoicesChanged directly.
236      */
237     private void initialize() {
238         assert mNativeTtsPlatformImplAndroid != 0;
239
240         // Note: Android supports multiple speech engines, but querying the
241         // metadata about all of them is expensive. So we deliberately only
242         // support the default speech engine, and expose the different
243         // supported languages for the default engine as different voices.
244         String defaultEngineName = mTextToSpeech.getDefaultEngine();
245         String engineLabel = defaultEngineName;
246         for (TextToSpeech.EngineInfo info : mTextToSpeech.getEngines()) {
247             if (info.name.equals(defaultEngineName)) engineLabel = info.label;
248         }
249         Locale[] locales = Locale.getAvailableLocales();
250         mVoices = new ArrayList<TtsVoice>();
251         for (int i = 0; i < locales.length; ++i) {
252             if (!locales[i].getVariant().isEmpty()) continue;
253             if (mTextToSpeech.isLanguageAvailable(locales[i]) > 0) {
254                 String name = locales[i].getDisplayLanguage();
255                 if (!locales[i].getCountry().isEmpty()) {
256                     name += " " + locales[i].getDisplayCountry();
257                 }
258                 TtsVoice voice = new TtsVoice(name, locales[i].toString());
259                 mVoices.add(voice);
260             }
261         }
262
263         mInitialized = true;
264         nativeVoicesChanged(mNativeTtsPlatformImplAndroid);
265
266         if (mPendingUtterance != null) mPendingUtterance.speak();
267     }
268
269     private native void nativeVoicesChanged(long nativeTtsPlatformImplAndroid);
270     private native void nativeOnEndEvent(long nativeTtsPlatformImplAndroid, int utteranceId);
271     private native void nativeOnStartEvent(long nativeTtsPlatformImplAndroid, int utteranceId);
272     private native void nativeOnErrorEvent(long nativeTtsPlatformImplAndroid, int utteranceId);
273 }