- add sources.
[platform/framework/web/crosswalk.git] / src / chrome / android / java / src / org / chromium / chrome / browser / TtsPlatformImpl.java
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.
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 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;
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 int mNativeTtsPlatformImplAndroid;
38     private final TextToSpeech mTextToSpeech;
39     private boolean mInitialized;
40     private ArrayList<TtsVoice> mVoices;
41     private String mCurrentLanguage;
42
43     private TtsPlatformImpl(int nativeTtsPlatformImplAndroid, Context context) {
44         mInitialized = false;
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() {
50                             @Override
51                             public void run() {
52                                 initialize();
53                             }
54                         });
55                     }
56                 }
57             });
58         mTextToSpeech.setOnUtteranceProgressListener(new UtteranceProgressListener() {
59                 public void onDone(final String utteranceId) {
60                     ThreadUtils.runOnUiThread(new Runnable() {
61                         @Override
62                         public void run() {
63                             if (mNativeTtsPlatformImplAndroid != 0) {
64                                 nativeOnEndEvent(mNativeTtsPlatformImplAndroid,
65                                                  Integer.parseInt(utteranceId));
66                             }
67                         }
68                     });
69                 }
70
71                 public void onError(final String utteranceId) {
72                     ThreadUtils.runOnUiThread(new Runnable() {
73                         @Override
74                         public void run() {
75                             if (mNativeTtsPlatformImplAndroid != 0) {
76                                 nativeOnErrorEvent(mNativeTtsPlatformImplAndroid,
77                                                    Integer.parseInt(utteranceId));
78                             }
79                         }
80                     });
81                 }
82
83                 public void onStart(final String utteranceId) {
84                     ThreadUtils.runOnUiThread(new Runnable() {
85                         @Override
86                         public void run() {
87                             if (mNativeTtsPlatformImplAndroid != 0) {
88                                 nativeOnStartEvent(mNativeTtsPlatformImplAndroid,
89                                                    Integer.parseInt(utteranceId));
90                             }
91                         }
92                     });
93                 }
94             });
95     };
96
97     /**
98      * Create a TtsPlatformImpl object, which is owned by TtsPlatformImplAndroid
99      * on the C++ side.
100      *
101      * @param nativeTtsPlatformImplAndroid The C++ object that owns us.
102      * @param context The app context.
103      */
104     @CalledByNative
105     private static TtsPlatformImpl create(int nativeTtsPlatformImplAndroid,
106                                           Context context) {
107         return new TtsPlatformImpl(nativeTtsPlatformImplAndroid, context);
108     }
109
110     /**
111      * Called when our C++ counterpoint is deleted. Clear the handle to our
112      * native C++ object, ensuring it's never called.
113      */
114     @CalledByNative
115     private void destroy() {
116         mNativeTtsPlatformImplAndroid = 0;
117     }
118
119     /**
120      * @return true if our TextToSpeech object is initialized and we've
121      * finished scanning the list of voices.
122      */
123     @CalledByNative
124     private boolean isInitialized() {
125         return mInitialized;
126     }
127
128     /**
129      * @return the number of voices.
130      */
131     @CalledByNative
132     private int getVoiceCount() {
133         assert mInitialized == true;
134         return mVoices.size();
135     }
136
137     /**
138      * @return the name of the voice at a given index.
139      */
140     @CalledByNative
141     private String getVoiceName(int voiceIndex) {
142         assert mInitialized == true;
143         return mVoices.get(voiceIndex).mName;
144     }
145
146     /**
147      * @return the language of the voice at a given index.
148      */
149     @CalledByNative
150     private String getVoiceLanguage(int voiceIndex) {
151         assert mInitialized == true;
152         return mVoices.get(voiceIndex).mLanguage;
153     }
154
155     /**
156      * Attempt to start speaking an utterance. If it returns true, will call back on
157      * start and end.
158      *
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.
167      */
168     @CalledByNative
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;
175         }
176
177         mTextToSpeech.setSpeechRate(rate);
178         mTextToSpeech.setPitch(pitch);
179         HashMap<String, String> params = new HashMap<String, String>();
180         if (volume != 1.0) {
181             params.put(TextToSpeech.Engine.KEY_PARAM_VOLUME, Double.toString(volume));
182         }
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);
186     }
187
188     /**
189      * Stop the current utterance.
190      */
191     @CalledByNative
192     private void stop() {
193         assert mInitialized == true;
194         mTextToSpeech.stop();
195     }
196
197     /**
198      * Note: we enforce that this method is called on the UI thread, so
199      * we can call nativeVoicesChanged directly.
200      */
201     private void initialize() {
202         assert mNativeTtsPlatformImplAndroid != 0;
203
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;
212         }
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();
221                 }
222                 TtsVoice voice = new TtsVoice(name, locales[i].toString());
223                 mVoices.add(voice);
224             }
225         }
226
227         mInitialized = true;
228         nativeVoicesChanged(mNativeTtsPlatformImplAndroid);
229     }
230
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);
235 }