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 ¶meters, 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