1 /****************************************************************************
2 **
3 ** Copyright (C) 2015 The Qt Company Ltd.
4 ** Contact: http://www.qt.io/licensing/
5 **
6 ** This file is part of the Qt Speech module of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:LGPL3$
9 ** Commercial License Usage
10 ** Licensees holding valid commercial Qt licenses may use this file in
11 ** accordance with the commercial license agreement provided with the
12 ** Software or, alternatively, in accordance with the terms contained in
13 ** a written agreement between you and The Qt Company. For licensing terms
14 ** and conditions see http://www.qt.io/terms-conditions. For further
15 ** information use the contact form at http://www.qt.io/contact-us.
16 **
17 ** GNU Lesser General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU Lesser
19 ** General Public License version 3 as published by the Free Software
20 ** Foundation and appearing in the file LICENSE.LGPLv3 included in the
21 ** packaging of this file. Please review the following information to
22 ** ensure the GNU Lesser General Public License version 3 requirements
23 ** will be met: https://www.gnu.org/licenses/lgpl.html.
24 **
25 ** GNU General Public License Usage
26 ** Alternatively, this file may be used under the terms of the GNU
27 ** General Public License version 2.0 or later as published by the Free
28 ** Software Foundation and appearing in the file LICENSE.GPL included in
29 ** the packaging of this file. Please review the following information to
30 ** ensure the GNU General Public License version 2.0 requirements will be
31 ** met: http://www.gnu.org/licenses/gpl-2.0.html.
32 **
33 ** $QT_END_LICENSE$
34 **
35 ****************************************************************************/
36 #include "qtexttospeech_android.h"
37 
38 #include <jni.h>
39 #include <QtCore/private/qjnihelpers_p.h>
40 
41 QT_BEGIN_NAMESPACE
42 
43 static jclass g_qtSpeechClass = 0;
44 
45 typedef QMap<jlong, QTextToSpeechEngineAndroid *> TextToSpeechMap;
Q_GLOBAL_STATIC(TextToSpeechMap,textToSpeechMap)46 Q_GLOBAL_STATIC(TextToSpeechMap, textToSpeechMap)
47 
48 static void notifyError(JNIEnv *env, jobject thiz, jlong id)
49 {
50     Q_UNUSED(env);
51     Q_UNUSED(thiz);
52 
53     QTextToSpeechEngineAndroid *const tts = (*textToSpeechMap)[id];
54     if (!tts)
55         return;
56 
57     QMetaObject::invokeMethod(tts, "processNotifyError", Qt::AutoConnection);
58 }
59 
notifyReady(JNIEnv * env,jobject thiz,jlong id)60 static void notifyReady(JNIEnv *env, jobject thiz, jlong id)
61 {
62     Q_UNUSED(env);
63     Q_UNUSED(thiz);
64 
65     QTextToSpeechEngineAndroid *const tts = (*textToSpeechMap)[id];
66     if (!tts)
67         return;
68 
69     QMetaObject::invokeMethod(tts, "processNotifyReady", Qt::AutoConnection);
70 }
71 
notifySpeaking(JNIEnv * env,jobject thiz,jlong id)72 static void notifySpeaking(JNIEnv *env, jobject thiz, jlong id)
73 {
74     Q_UNUSED(env);
75     Q_UNUSED(thiz);
76 
77     QTextToSpeechEngineAndroid *const tts = (*textToSpeechMap)[id];
78     if (!tts)
79         return;
80 
81     QMetaObject::invokeMethod(tts, "processNotifySpeaking", Qt::AutoConnection);
82 }
83 
JNI_OnLoad(JavaVM * vm,void *)84 Q_DECL_EXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void */*reserved*/)
85 {
86     static bool initialized = false;
87     if (initialized)
88         return JNI_VERSION_1_6;
89     initialized = true;
90 
91     typedef union {
92         JNIEnv *nativeEnvironment;
93         void *venv;
94     } UnionJNIEnvToVoid;
95 
96     UnionJNIEnvToVoid uenv;
97     uenv.venv = NULL;
98 
99     if (vm->GetEnv(&uenv.venv, JNI_VERSION_1_6) != JNI_OK)
100         return JNI_ERR;
101 
102     JNIEnv *jniEnv = uenv.nativeEnvironment;
103     jclass clazz = jniEnv->FindClass("org/qtproject/qt5/android/speech/QtTextToSpeech");
104 
105     static const JNINativeMethod methods[] = {
106         {"notifyError", "(J)V", reinterpret_cast<void *>(notifyError)},
107         {"notifyReady", "(J)V", reinterpret_cast<void *>(notifyReady)},
108         {"notifySpeaking", "(J)V", reinterpret_cast<void *>(notifySpeaking)}
109     };
110 
111     if (clazz) {
112         g_qtSpeechClass = static_cast<jclass>(jniEnv->NewGlobalRef(clazz));
113         if (jniEnv->RegisterNatives(g_qtSpeechClass,
114                                     methods,
115                                     sizeof(methods) / sizeof(methods[0])) != JNI_OK) {
116             return JNI_ERR;
117         }
118     }
119 
120     return JNI_VERSION_1_6;
121 }
122 
QTextToSpeechEngineAndroid(const QVariantMap & parameters,QObject * parent)123 QTextToSpeechEngineAndroid::QTextToSpeechEngineAndroid(const QVariantMap &parameters, QObject *parent)
124     : QTextToSpeechEngine(parent)
125     , m_speech()
126     , m_state(QTextToSpeech::BackendError)
127     , m_text()
128 {
129     Q_UNUSED(parameters)
130     Q_ASSERT(g_qtSpeechClass);
131 
132     const jlong id = reinterpret_cast<jlong>(this);
133     m_speech = QJNIObjectPrivate::callStaticObjectMethod(g_qtSpeechClass,
134                                                          "open",
135                                                          "(Landroid/content/Context;J)Lorg/qtproject/qt5/android/speech/QtTextToSpeech;",
136                                                          QtAndroidPrivate::context(),
137                                                          id);
138     (*textToSpeechMap)[id] = this;
139 }
140 
~QTextToSpeechEngineAndroid()141 QTextToSpeechEngineAndroid::~QTextToSpeechEngineAndroid()
142 {
143     textToSpeechMap->remove(reinterpret_cast<jlong>(this));
144     m_speech.callMethod<void>("shutdown");
145 }
146 
say(const QString & text)147 void QTextToSpeechEngineAndroid::say(const QString &text)
148 {
149     if (text.isEmpty())
150         return;
151 
152     if (m_state == QTextToSpeech::Speaking)
153         stop();
154 
155     m_text = text;
156     m_speech.callMethod<void>("say", "(Ljava/lang/String;)V", QJNIObjectPrivate::fromString(m_text).object());
157 }
158 
state() const159 QTextToSpeech::State QTextToSpeechEngineAndroid::state() const
160 {
161     return m_state;
162 }
163 
setState(QTextToSpeech::State state)164 void QTextToSpeechEngineAndroid::setState(QTextToSpeech::State state)
165 {
166     if (m_state == state)
167         return;
168 
169     m_state = state;
170     emit stateChanged(m_state);
171 }
172 
processNotifyReady()173 void QTextToSpeechEngineAndroid::processNotifyReady()
174 {
175     if (m_state != QTextToSpeech::Paused)
176         setState(QTextToSpeech::Ready);
177 }
178 
processNotifyError()179 void QTextToSpeechEngineAndroid::processNotifyError()
180 {
181     setState(QTextToSpeech::BackendError);
182 }
183 
processNotifySpeaking()184 void QTextToSpeechEngineAndroid::processNotifySpeaking()
185 {
186     setState(QTextToSpeech::Speaking);
187 }
188 
stop()189 void QTextToSpeechEngineAndroid::stop()
190 {
191     if (m_state == QTextToSpeech::Ready)
192         return;
193 
194     m_speech.callMethod<void>("stop", "()V");
195     setState(QTextToSpeech::Ready);
196 }
197 
pause()198 void QTextToSpeechEngineAndroid::pause()
199 {
200     if (m_state == QTextToSpeech::Paused)
201         return;
202 
203     m_speech.callMethod<void>("stop", "()V");
204     setState(QTextToSpeech::Paused);
205 }
206 
resume()207 void QTextToSpeechEngineAndroid::resume()
208 {
209     if (m_state != QTextToSpeech::Paused)
210         return;
211 
212     say(m_text);
213 }
214 
pitch() const215 double QTextToSpeechEngineAndroid::pitch() const
216 {
217     jfloat pitch = m_speech.callMethod<jfloat>("pitch");
218     return double(pitch - 1.0f);
219 }
220 
setPitch(double pitch)221 bool QTextToSpeechEngineAndroid::setPitch(double pitch)
222 {
223     // 0 == SUCCESS and 1.0 == Android API's normal pitch.
224     return m_speech.callMethod<int>("setPitch", "(F)I", pitch + 1.0f) == 0;
225 }
226 
rate() const227 double QTextToSpeechEngineAndroid::rate() const
228 {
229     jfloat rate = m_speech.callMethod<jfloat>("rate");
230     return double(rate - 1.0f);
231 }
232 
setRate(double rate)233 bool QTextToSpeechEngineAndroid::setRate(double rate)
234 {
235     // 0 == SUCCESS and 1.0 == Android API's normal rate.
236     return (m_speech.callMethod<int>("setRate", "(F)I", rate + 1.0f) == 0);
237 }
238 
volume() const239 double QTextToSpeechEngineAndroid::volume() const
240 {
241     jfloat volume = m_speech.callMethod<jfloat>("volume");
242     return volume;
243 }
244 
setVolume(double volume)245 bool QTextToSpeechEngineAndroid::setVolume(double volume)
246 {
247     // 0 == SUCCESS
248     return m_speech.callMethod<jint>("setVolume", "(F)I", float(volume)) == 0;
249 }
250 
availableLocales() const251 QVector<QLocale> QTextToSpeechEngineAndroid::availableLocales() const
252 {
253     auto locales = m_speech.callObjectMethod("getAvailableLocales", "()Ljava/util/List;");
254     int count = locales.callMethod<jint>("size");
255     QVector<QLocale> result;
256     result.reserve(count);
257     for (int i = 0; i < count; ++i) {
258         auto locale = locales.callObjectMethod("get", "(I)Ljava/lang/Object;", i);
259         auto localeLanguage = locale.callObjectMethod<jstring>("getLanguage").toString();
260         auto localeCountry = locale.callObjectMethod<jstring>("getCountry").toString();
261         if (!localeCountry.isEmpty())
262             localeLanguage += QString("_%1").arg(localeCountry).toUpper();
263         result << QLocale(localeLanguage);
264     }
265     return result;
266 }
267 
setLocale(const QLocale & locale)268 bool QTextToSpeechEngineAndroid::setLocale(const QLocale &locale)
269 {
270     QStringList parts = locale.name().split('_');
271 
272     if (parts.length() != 2)
273         return false;
274 
275     QString languageCode = parts.at(0);
276     QString countryCode = parts.at(1);
277 
278     QJNIObjectPrivate jLocale("java/util/Locale", "(Ljava/lang/String;Ljava/lang/String;)V",
279                               QJNIObjectPrivate::fromString(languageCode).object(),
280                               QJNIObjectPrivate::fromString(countryCode).object());
281 
282     return m_speech.callMethod<jboolean>("setLocale", "(Ljava/util/Locale;)Z", jLocale.object());
283 }
284 
locale() const285 QLocale QTextToSpeechEngineAndroid::locale() const
286 {
287     auto locale = m_speech.callObjectMethod("getLocale", "()Ljava/util/Locale;");
288     if (locale.isValid()) {
289         auto localeLanguage = locale.callObjectMethod<jstring>("getLanguage").toString();
290         auto localeCountry = locale.callObjectMethod<jstring>("getCountry").toString();
291         if (!localeCountry.isEmpty())
292             localeLanguage += QString("_%1").arg(localeCountry).toUpper();
293         return QLocale(localeLanguage);
294     }
295     return QLocale();
296 }
297 
javaVoiceObjectToQVoice(QJNIObjectPrivate & obj) const298 QVoice QTextToSpeechEngineAndroid::javaVoiceObjectToQVoice(QJNIObjectPrivate &obj) const
299 {
300     auto voiceName = obj.callObjectMethod<jstring>("getName").toString();
301     QVoice::Gender gender;
302     if (voiceName.contains(QStringLiteral("#male"))) {
303         gender = QVoice::Male;
304     } else if (voiceName.contains(QStringLiteral("#female"))) {
305         gender = QVoice::Female;
306     } else {
307         gender = QVoice::Unknown;
308     }
309     return createVoice(voiceName, gender, QVoice::Other, voiceName);
310 }
311 
availableVoices() const312 QVector<QVoice> QTextToSpeechEngineAndroid::availableVoices() const
313 {
314     auto voices = m_speech.callObjectMethod("getAvailableVoices", "()Ljava/util/List;");
315     int count = voices.callMethod<jint>("size");
316     QVector<QVoice> result;
317     result.reserve(count);
318     for (int i = 0; i < count; ++i) {
319         auto voice = voices.callObjectMethod("get", "(I)Ljava/lang/Object;", i);
320         result << javaVoiceObjectToQVoice(voice);
321     }
322     return result;
323 }
324 
setVoice(const QVoice & voice)325 bool QTextToSpeechEngineAndroid::setVoice(const QVoice &voice)
326 {
327     return m_speech.callMethod<jboolean>("setVoice", "(Ljava/lang/String;)Z",
328                                          QJNIObjectPrivate::fromString(voiceData(voice).toString()).object());
329 }
330 
voice() const331 QVoice QTextToSpeechEngineAndroid::voice() const
332 {
333     auto voice = m_speech.callObjectMethod("getVoice", "()Ljava/lang/Object;");
334     if (voice.isValid()) {
335         return javaVoiceObjectToQVoice(voice);
336     }
337     return QVoice();
338 }
339 
340 QT_END_NAMESPACE
341