1 /****************************************************************************
2 **
3 ** Copyright (C) 2016 The Qt Company Ltd.
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of the QtPositioning module of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:LGPL$
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 https://www.qt.io/terms-conditions. For further
15 ** information use the contact form at https://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.LGPL3 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-3.0.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 (at your option) the GNU General
28 ** Public license version 3 or any later version approved by the KDE Free
29 ** Qt Foundation. The licenses are as published by the Free Software
30 ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31 ** included in the packaging of this file. Please review the following
32 ** information to ensure the GNU General Public License requirements will
33 ** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34 ** https://www.gnu.org/licenses/gpl-3.0.html.
35 **
36 ** $QT_END_LICENSE$
37 **
38 ****************************************************************************/
39 
40 #include <QDateTime>
41 #include <QDebug>
42 #include <QMap>
43 #include <QRandomGenerator>
44 #include <QtGlobal>
45 #include <QtCore/private/qjnihelpers_p.h>
46 #include <android/log.h>
47 #include <QGeoPositionInfo>
48 #include "qgeopositioninfosource_android_p.h"
49 #include "qgeosatelliteinfosource_android_p.h"
50 
51 #include "jnipositioning.h"
52 
53 static JavaVM *javaVM = nullptr;
54 static jclass positioningClass;
55 
56 static jmethodID providerListMethodId;
57 static jmethodID lastKnownPositionMethodId;
58 static jmethodID startUpdatesMethodId;
59 static jmethodID stopUpdatesMethodId;
60 static jmethodID requestUpdateMethodId;
61 static jmethodID startSatelliteUpdatesMethodId;
62 
63 static const char logTag[] = "QtPositioning";
64 static const char classErrorMsg[] = "Can't find class \"%s\"";
65 static const char methodErrorMsg[] = "Can't find method \"%s%s\"";
66 
67 namespace AndroidPositioning {
68     typedef QMap<int, QGeoPositionInfoSourceAndroid * > PositionSourceMap;
69     typedef QMap<int, QGeoSatelliteInfoSourceAndroid * > SatelliteSourceMap;
70 
71     Q_GLOBAL_STATIC(PositionSourceMap, idToPosSource)
72 
73     Q_GLOBAL_STATIC(SatelliteSourceMap, idToSatSource)
74 
75     struct AttachedJNIEnv
76     {
AttachedJNIEnvAndroidPositioning::AttachedJNIEnv77         AttachedJNIEnv()
78         {
79             attached = false;
80             if (javaVM && javaVM->GetEnv(reinterpret_cast<void**>(&jniEnv), JNI_VERSION_1_6) < 0) {
81                 if (javaVM->AttachCurrentThread(&jniEnv, nullptr) < 0) {
82                     __android_log_print(ANDROID_LOG_ERROR, logTag, "AttachCurrentThread failed");
83                     jniEnv = nullptr;
84                     return;
85                 }
86                 attached = true;
87             }
88         }
89 
~AttachedJNIEnvAndroidPositioning::AttachedJNIEnv90         ~AttachedJNIEnv()
91         {
92             if (attached)
93                 javaVM->DetachCurrentThread();
94         }
95         bool attached;
96         JNIEnv *jniEnv;
97     };
98 
registerPositionInfoSource(QObject * obj)99     int registerPositionInfoSource(QObject *obj)
100     {
101         static bool firstInit = true;
102         if (firstInit) {
103             firstInit = false;
104         }
105 
106         int key = -1;
107         if (obj->inherits("QGeoPositionInfoSource")) {
108             QGeoPositionInfoSourceAndroid *src = qobject_cast<QGeoPositionInfoSourceAndroid *>(obj);
109             Q_ASSERT(src);
110             do {
111                 key = qAbs(int(QRandomGenerator::global()->generate()));
112             } while (idToPosSource()->contains(key));
113 
114             idToPosSource()->insert(key, src);
115         } else if (obj->inherits("QGeoSatelliteInfoSource")) {
116             QGeoSatelliteInfoSourceAndroid *src = qobject_cast<QGeoSatelliteInfoSourceAndroid *>(obj);
117             Q_ASSERT(src);
118             do {
119                 key = qAbs(int(QRandomGenerator::global()->generate()));
120             } while (idToSatSource()->contains(key));
121 
122             idToSatSource()->insert(key, src);
123         }
124 
125         return key;
126     }
127 
unregisterPositionInfoSource(int key)128     void unregisterPositionInfoSource(int key)
129     {
130         idToPosSource()->remove(key);
131         idToSatSource()->remove(key);
132     }
133 
134     enum PositionProvider
135     {
136         PROVIDER_GPS = 0,
137         PROVIDER_NETWORK = 1,
138         PROVIDER_PASSIVE = 2
139     };
140 
141 
availableProviders()142     QGeoPositionInfoSource::PositioningMethods availableProviders()
143     {
144         QGeoPositionInfoSource::PositioningMethods ret = QGeoPositionInfoSource::NoPositioningMethods;
145         AttachedJNIEnv env;
146         if (!env.jniEnv)
147             return ret;
148         jintArray jProviders = static_cast<jintArray>(env.jniEnv->CallStaticObjectMethod(
149                                                           positioningClass, providerListMethodId));
150         jint *providers = env.jniEnv->GetIntArrayElements(jProviders, nullptr);
151         const int size = env.jniEnv->GetArrayLength(jProviders);
152         for (int i = 0; i < size; i++) {
153             switch (providers[i]) {
154             case PROVIDER_GPS:
155                 ret |= QGeoPositionInfoSource::SatellitePositioningMethods;
156                 break;
157             case PROVIDER_NETWORK:
158                 ret |= QGeoPositionInfoSource::NonSatellitePositioningMethods;
159                 break;
160             case PROVIDER_PASSIVE:
161                 //we ignore as Qt doesn't have interface for it right now
162                 break;
163             default:
164                 __android_log_print(ANDROID_LOG_INFO, logTag, "Unknown positioningMethod");
165             }
166         }
167 
168         env.jniEnv->ReleaseIntArrayElements(jProviders, providers, 0);
169         env.jniEnv->DeleteLocalRef(jProviders);
170 
171         return ret;
172     }
173 
174     //caching originally taken from corelib/kernel/qjni.cpp
175     typedef QHash<QByteArray, jmethodID> JMethodIDHash;
Q_GLOBAL_STATIC(JMethodIDHash,cachedMethodID)176     Q_GLOBAL_STATIC(JMethodIDHash, cachedMethodID)
177 
178     static jmethodID getCachedMethodID(JNIEnv *env,
179                                        jclass clazz,
180                                        const char *name,
181                                        const char *sig)
182     {
183         jmethodID id = nullptr;
184         uint offset_name = qstrlen(name);
185         uint offset_signal = qstrlen(sig);
186         QByteArray key(int(offset_name + offset_signal), Qt::Uninitialized);
187         memcpy(key.data(), name, offset_name);
188         memcpy(key.data()+offset_name, sig, offset_signal);
189         QHash<QByteArray, jmethodID>::iterator it = cachedMethodID->find(key);
190         if (it == cachedMethodID->end()) {
191             id = env->GetMethodID(clazz, name, sig);
192             if (env->ExceptionCheck()) {
193                 id = nullptr;
194     #ifdef QT_DEBUG
195                 env->ExceptionDescribe();
196     #endif // QT_DEBUG
197                 env->ExceptionClear();
198             }
199 
200             cachedMethodID->insert(key, id);
201         } else {
202             id = it.value();
203         }
204         return id;
205     }
206 
positionInfoFromJavaLocation(JNIEnv * jniEnv,const jobject & location)207     QGeoPositionInfo positionInfoFromJavaLocation(JNIEnv * jniEnv, const jobject &location)
208     {
209         QGeoPositionInfo info;
210         jclass thisClass = jniEnv->GetObjectClass(location);
211         if (!thisClass)
212             return QGeoPositionInfo();
213 
214         jmethodID mid = getCachedMethodID(jniEnv, thisClass, "getLatitude", "()D");
215         jdouble latitude = jniEnv->CallDoubleMethod(location, mid);
216         mid = getCachedMethodID(jniEnv, thisClass, "getLongitude", "()D");
217         jdouble longitude = jniEnv->CallDoubleMethod(location, mid);
218         QGeoCoordinate coordinate(latitude, longitude);
219 
220         //altitude
221         mid = getCachedMethodID(jniEnv, thisClass, "hasAltitude", "()Z");
222         jboolean attributeExists = jniEnv->CallBooleanMethod(location, mid);
223         if (attributeExists) {
224             mid = getCachedMethodID(jniEnv, thisClass, "getAltitude", "()D");
225             jdouble value = jniEnv->CallDoubleMethod(location, mid);
226             if (value != 0.0)
227             {
228                 coordinate.setAltitude(value);
229             }
230         }
231 
232         info.setCoordinate(coordinate);
233 
234         //time stamp
235         mid = getCachedMethodID(jniEnv, thisClass, "getTime", "()J");
236         jlong timestamp = jniEnv->CallLongMethod(location, mid);
237         info.setTimestamp(QDateTime::fromMSecsSinceEpoch(timestamp, Qt::UTC));
238 
239         //horizontal accuracy
240         mid = getCachedMethodID(jniEnv, thisClass, "hasAccuracy", "()Z");
241         attributeExists = jniEnv->CallBooleanMethod(location, mid);
242         if (attributeExists) {
243             mid = getCachedMethodID(jniEnv, thisClass, "getAccuracy", "()F");
244             jfloat accuracy = jniEnv->CallFloatMethod(location, mid);
245             if (accuracy != 0.0)
246             {
247                 info.setAttribute(QGeoPositionInfo::HorizontalAccuracy, qreal(accuracy));
248             }
249         }
250 
251         //vertical accuracy
252         mid = getCachedMethodID(jniEnv, thisClass, "hasVerticalAccuracy", "()Z");
253         if (mid) {
254             attributeExists = jniEnv->CallBooleanMethod(location, mid);
255             if (attributeExists) {
256                 mid = getCachedMethodID(jniEnv, thisClass, "getVerticalAccuracyMeters", "()F");
257                 if (mid) {
258                     jfloat accuracy = jniEnv->CallFloatMethod(location, mid);
259                     if (accuracy != 0.0)
260                     {
261                         info.setAttribute(QGeoPositionInfo::VerticalAccuracy, qreal(accuracy));
262                     }
263                 }
264             }
265         }
266 
267         if (!mid)
268             jniEnv->ExceptionClear();
269 
270         //ground speed
271         mid = getCachedMethodID(jniEnv, thisClass, "hasSpeed", "()Z");
272         attributeExists = jniEnv->CallBooleanMethod(location, mid);
273         if (attributeExists) {
274             mid = getCachedMethodID(jniEnv, thisClass, "getSpeed", "()F");
275             jfloat speed = jniEnv->CallFloatMethod(location, mid);
276             if (speed != 0)
277             {
278                 info.setAttribute(QGeoPositionInfo::GroundSpeed, qreal(speed));
279             }
280         }
281 
282         //bearing
283         mid = getCachedMethodID(jniEnv, thisClass, "hasBearing", "()Z");
284         attributeExists = jniEnv->CallBooleanMethod(location, mid);
285         if (attributeExists) {
286             mid = getCachedMethodID(jniEnv, thisClass, "getBearing", "()F");
287             jfloat bearing = jniEnv->CallFloatMethod(location, mid);
288             if (bearing != 0.0)
289             {
290                 info.setAttribute(QGeoPositionInfo::Direction, qreal(bearing));
291             }
292         }
293 
294         jniEnv->DeleteLocalRef(thisClass);
295         return info;
296     }
297 
satelliteInfoFromJavaLocation(JNIEnv * jniEnv,jobjectArray satellites,QList<QGeoSatelliteInfo> * usedInFix)298     QList<QGeoSatelliteInfo> satelliteInfoFromJavaLocation(JNIEnv *jniEnv,
299                                                            jobjectArray satellites,
300                                                            QList<QGeoSatelliteInfo>* usedInFix)
301     {
302         QList<QGeoSatelliteInfo> sats;
303         jsize length = jniEnv->GetArrayLength(satellites);
304         for (int i = 0; i<length; i++) {
305             jobject element = jniEnv->GetObjectArrayElement(satellites, i);
306             if (jniEnv->ExceptionOccurred()) {
307                 qWarning() << "Cannot process all satellite data due to exception.";
308                 break;
309             }
310 
311             jclass thisClass = jniEnv->GetObjectClass(element);
312             if (!thisClass)
313                 continue;
314 
315             QGeoSatelliteInfo info;
316 
317             //signal strength
318             jmethodID mid = getCachedMethodID(jniEnv, thisClass, "getSnr", "()F");
319             jfloat snr = jniEnv->CallFloatMethod(element, mid);
320             info.setSignalStrength(int(snr));
321 
322             //ignore any satellite with no signal whatsoever
323             if (qFuzzyIsNull(snr))
324                 continue;
325 
326             //prn
327             mid = getCachedMethodID(jniEnv, thisClass, "getPrn", "()I");
328             jint prn = jniEnv->CallIntMethod(element, mid);
329             info.setSatelliteIdentifier(prn);
330 
331             if (prn >= 1 && prn <= 32)
332                 info.setSatelliteSystem(QGeoSatelliteInfo::GPS);
333             else if (prn >= 65 && prn <= 96)
334                 info.setSatelliteSystem(QGeoSatelliteInfo::GLONASS);
335 
336             //azimuth
337             mid = getCachedMethodID(jniEnv, thisClass, "getAzimuth", "()F");
338             jfloat azimuth = jniEnv->CallFloatMethod(element, mid);
339             info.setAttribute(QGeoSatelliteInfo::Azimuth, qreal(azimuth));
340 
341             //elevation
342             mid = getCachedMethodID(jniEnv, thisClass, "getElevation", "()F");
343             jfloat elevation = jniEnv->CallFloatMethod(element, mid);
344             info.setAttribute(QGeoSatelliteInfo::Elevation, qreal(elevation));
345 
346             //used in a fix
347             mid = getCachedMethodID(jniEnv, thisClass, "usedInFix", "()Z");
348             jboolean inFix = jniEnv->CallBooleanMethod(element, mid);
349 
350             sats.append(info);
351 
352             if (inFix)
353                 usedInFix->append(info);
354 
355             jniEnv->DeleteLocalRef(thisClass);
356             jniEnv->DeleteLocalRef(element);
357         }
358 
359         return sats;
360     }
361 
lastKnownPosition(bool fromSatellitePositioningMethodsOnly)362     QGeoPositionInfo lastKnownPosition(bool fromSatellitePositioningMethodsOnly)
363     {
364         AttachedJNIEnv env;
365         if (!env.jniEnv)
366             return QGeoPositionInfo();
367 
368         if (!requestionPositioningPermissions(env.jniEnv))
369             return {};
370 
371         jobject location = env.jniEnv->CallStaticObjectMethod(positioningClass,
372                                                               lastKnownPositionMethodId,
373                                                               fromSatellitePositioningMethodsOnly);
374         if (location == nullptr)
375             return QGeoPositionInfo();
376 
377         const QGeoPositionInfo info = positionInfoFromJavaLocation(env.jniEnv, location);
378         env.jniEnv->DeleteLocalRef(location);
379 
380         return info;
381     }
382 
positioningMethodToInt(QGeoPositionInfoSource::PositioningMethods m)383     inline int positioningMethodToInt(QGeoPositionInfoSource::PositioningMethods m)
384     {
385         int providerSelection = 0;
386         if (m & QGeoPositionInfoSource::SatellitePositioningMethods)
387             providerSelection |= 1;
388         if (m & QGeoPositionInfoSource::NonSatellitePositioningMethods)
389             providerSelection |= 2;
390 
391         return providerSelection;
392     }
393 
startUpdates(int androidClassKey)394     QGeoPositionInfoSource::Error startUpdates(int androidClassKey)
395     {
396         AttachedJNIEnv env;
397         if (!env.jniEnv)
398             return QGeoPositionInfoSource::UnknownSourceError;
399 
400         QGeoPositionInfoSourceAndroid *source = AndroidPositioning::idToPosSource()->value(androidClassKey);
401 
402         if (source) {
403             if (!requestionPositioningPermissions(env.jniEnv))
404                 return QGeoPositionInfoSource::AccessError;
405 
406             int errorCode = env.jniEnv->CallStaticIntMethod(positioningClass, startUpdatesMethodId,
407                                              androidClassKey,
408                                              positioningMethodToInt(source->preferredPositioningMethods()),
409                                              source->updateInterval());
410             switch (errorCode) {
411             case 0:
412             case 1:
413             case 2:
414             case 3:
415                 return static_cast<QGeoPositionInfoSource::Error>(errorCode);
416             default:
417                 break;
418             }
419         }
420 
421         return QGeoPositionInfoSource::UnknownSourceError;
422     }
423 
424     //used for stopping regular and single updates
stopUpdates(int androidClassKey)425     void stopUpdates(int androidClassKey)
426     {
427         AttachedJNIEnv env;
428         if (!env.jniEnv)
429             return;
430 
431         env.jniEnv->CallStaticVoidMethod(positioningClass, stopUpdatesMethodId, androidClassKey);
432     }
433 
requestUpdate(int androidClassKey)434     QGeoPositionInfoSource::Error requestUpdate(int androidClassKey)
435     {
436         AttachedJNIEnv env;
437         if (!env.jniEnv)
438             return QGeoPositionInfoSource::UnknownSourceError;
439 
440         QGeoPositionInfoSourceAndroid *source = AndroidPositioning::idToPosSource()->value(androidClassKey);
441 
442         if (source) {
443             if (!requestionPositioningPermissions(env.jniEnv))
444                 return QGeoPositionInfoSource::AccessError;
445 
446             int errorCode = env.jniEnv->CallStaticIntMethod(positioningClass, requestUpdateMethodId,
447                                              androidClassKey,
448                                              positioningMethodToInt(source->preferredPositioningMethods()));
449             switch (errorCode) {
450             case 0:
451             case 1:
452             case 2:
453             case 3:
454                 return static_cast<QGeoPositionInfoSource::Error>(errorCode);
455             default:
456                 break;
457             }
458         }
459         return QGeoPositionInfoSource::UnknownSourceError;
460     }
461 
startSatelliteUpdates(int androidClassKey,bool isSingleRequest,int requestTimeout)462     QGeoSatelliteInfoSource::Error startSatelliteUpdates(int androidClassKey, bool isSingleRequest, int requestTimeout)
463     {
464         AttachedJNIEnv env;
465         if (!env.jniEnv)
466             return QGeoSatelliteInfoSource::UnknownSourceError;
467 
468         QGeoSatelliteInfoSourceAndroid *source = AndroidPositioning::idToSatSource()->value(androidClassKey);
469 
470         if (source) {
471             if (!requestionPositioningPermissions(env.jniEnv))
472                 return QGeoSatelliteInfoSource::AccessError;
473 
474             int interval = source->updateInterval();
475             if (isSingleRequest)
476                 interval = requestTimeout;
477             int errorCode = env.jniEnv->CallStaticIntMethod(positioningClass, startSatelliteUpdatesMethodId,
478                                              androidClassKey,
479                                              interval, isSingleRequest);
480             switch (errorCode) {
481             case -1:
482             case 0:
483             case 1:
484             case 2:
485                 return static_cast<QGeoSatelliteInfoSource::Error>(errorCode);
486             default:
487                 qWarning() << "startSatelliteUpdates: Unknown error code " << errorCode;
488                 break;
489             }
490         }
491         return QGeoSatelliteInfoSource::UnknownSourceError;
492     }
493 
requestionPositioningPermissions(JNIEnv * env)494     bool requestionPositioningPermissions(JNIEnv *env)
495     {
496         using namespace QtAndroidPrivate;
497 
498         if (androidSdkVersion() < 23)
499             return true;
500 
501         // Android v23+ requires runtime permission check and requests
502         QString permission(QLatin1String("android.permission.ACCESS_FINE_LOCATION"));
503 
504         if (checkPermission(permission) == PermissionsResult::Denied) {
505             const QHash<QString, PermissionsResult> results =
506                     requestPermissionsSync(env, QStringList() << permission);
507             if (!results.contains(permission) || results[permission] == PermissionsResult::Denied) {
508                 qWarning() << "Position data not available due to missing permission " << permission;
509                 return false;
510             }
511         }
512 
513         return true;
514     }
515 }
516 
positionUpdated(JNIEnv * env,jobject,jobject location,jint androidClassKey,jboolean isSingleUpdate)517 static void positionUpdated(JNIEnv *env, jobject /*thiz*/, jobject location, jint androidClassKey, jboolean isSingleUpdate)
518 {
519     QGeoPositionInfo info = AndroidPositioning::positionInfoFromJavaLocation(env, location);
520 
521     QGeoPositionInfoSourceAndroid *source = AndroidPositioning::idToPosSource()->value(androidClassKey);
522     if (!source) {
523         qWarning("positionUpdated: source == 0");
524         return;
525     }
526 
527     //we need to invoke indirectly as the Looper thread is likely to be not the same thread
528     if (!isSingleUpdate)
529         QMetaObject::invokeMethod(source, "processPositionUpdate", Qt::AutoConnection,
530                               Q_ARG(QGeoPositionInfo, info));
531     else
532         QMetaObject::invokeMethod(source, "processSinglePositionUpdate", Qt::AutoConnection,
533                               Q_ARG(QGeoPositionInfo, info));
534 }
535 
locationProvidersDisabled(JNIEnv * env,jobject,jint androidClassKey)536 static void locationProvidersDisabled(JNIEnv *env, jobject /*thiz*/, jint androidClassKey)
537 {
538     Q_UNUSED(env);
539     QObject *source = AndroidPositioning::idToPosSource()->value(androidClassKey);
540     if (!source)
541         source = AndroidPositioning::idToSatSource()->value(androidClassKey);
542     if (!source) {
543         qWarning("locationProvidersDisabled: source == 0");
544         return;
545     }
546 
547     QMetaObject::invokeMethod(source, "locationProviderDisabled", Qt::AutoConnection);
548 }
549 
locationProvidersChanged(JNIEnv * env,jobject,jint androidClassKey)550 static void locationProvidersChanged(JNIEnv *env, jobject /*thiz*/, jint androidClassKey)
551 {
552     Q_UNUSED(env);
553     QObject *source = AndroidPositioning::idToPosSource()->value(androidClassKey);
554     if (!source) {
555         qWarning("locationProvidersChanged: source == 0");
556         return;
557     }
558 
559     QMetaObject::invokeMethod(source, "locationProvidersChanged", Qt::AutoConnection);
560 }
561 
satelliteUpdated(JNIEnv * env,jobject,jobjectArray satellites,jint androidClassKey,jboolean isSingleUpdate)562 static void satelliteUpdated(JNIEnv *env, jobject /*thiz*/, jobjectArray satellites, jint androidClassKey, jboolean isSingleUpdate)
563 {
564     QList<QGeoSatelliteInfo> inUse;
565     QList<QGeoSatelliteInfo> sats = AndroidPositioning::satelliteInfoFromJavaLocation(env, satellites, &inUse);
566 
567     QGeoSatelliteInfoSourceAndroid *source = AndroidPositioning::idToSatSource()->value(androidClassKey);
568     if (!source) {
569         qWarning("satelliteUpdated: source == 0");
570         return;
571     }
572 
573     QMetaObject::invokeMethod(source, "processSatelliteUpdateInView", Qt::AutoConnection,
574                               Q_ARG(QList<QGeoSatelliteInfo>, sats), Q_ARG(bool, isSingleUpdate));
575 
576     QMetaObject::invokeMethod(source, "processSatelliteUpdateInUse", Qt::AutoConnection,
577                               Q_ARG(QList<QGeoSatelliteInfo>, inUse), Q_ARG(bool, isSingleUpdate));
578 }
579 
580 
581 #define FIND_AND_CHECK_CLASS(CLASS_NAME) \
582 clazz = env->FindClass(CLASS_NAME); \
583 if (!clazz) { \
584     __android_log_print(ANDROID_LOG_FATAL, logTag, classErrorMsg, CLASS_NAME); \
585     return JNI_FALSE; \
586 }
587 
588 #define GET_AND_CHECK_STATIC_METHOD(VAR, CLASS, METHOD_NAME, METHOD_SIGNATURE) \
589 VAR = env->GetStaticMethodID(CLASS, METHOD_NAME, METHOD_SIGNATURE); \
590 if (!VAR) { \
591     __android_log_print(ANDROID_LOG_FATAL, logTag, methodErrorMsg, METHOD_NAME, METHOD_SIGNATURE); \
592     return JNI_FALSE; \
593 }
594 
595 static JNINativeMethod methods[] = {
596     {"positionUpdated", "(Landroid/location/Location;IZ)V", (void *)positionUpdated},
597     {"locationProvidersDisabled", "(I)V", (void *) locationProvidersDisabled},
598     {"satelliteUpdated", "([Landroid/location/GpsSatellite;IZ)V", (void *)satelliteUpdated},
599     {"locationProvidersChanged", "(I)V", (void *) locationProvidersChanged}
600 };
601 
registerNatives(JNIEnv * env)602 static bool registerNatives(JNIEnv *env)
603 {
604     jclass clazz;
605     FIND_AND_CHECK_CLASS("org/qtproject/qt5/android/positioning/QtPositioning");
606     positioningClass = static_cast<jclass>(env->NewGlobalRef(clazz));
607 
608     if (env->RegisterNatives(positioningClass, methods, sizeof(methods) / sizeof(methods[0])) < 0) {
609         __android_log_print(ANDROID_LOG_FATAL, logTag, "RegisterNatives failed");
610         return JNI_FALSE;
611     }
612 
613     GET_AND_CHECK_STATIC_METHOD(providerListMethodId, positioningClass, "providerList", "()[I");
614     GET_AND_CHECK_STATIC_METHOD(lastKnownPositionMethodId, positioningClass, "lastKnownPosition", "(Z)Landroid/location/Location;");
615     GET_AND_CHECK_STATIC_METHOD(startUpdatesMethodId, positioningClass, "startUpdates", "(III)I");
616     GET_AND_CHECK_STATIC_METHOD(stopUpdatesMethodId, positioningClass, "stopUpdates", "(I)V");
617     GET_AND_CHECK_STATIC_METHOD(requestUpdateMethodId, positioningClass, "requestUpdate", "(II)I");
618     GET_AND_CHECK_STATIC_METHOD(startSatelliteUpdatesMethodId, positioningClass, "startSatelliteUpdates", "(IIZ)I");
619 
620     return true;
621 }
622 
JNI_OnLoad(JavaVM * vm,void *)623 Q_DECL_EXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void * /*reserved*/)
624 {
625     static bool initialized = false;
626     if (initialized)
627         return JNI_VERSION_1_6;
628     initialized = true;
629 
630     typedef union {
631         JNIEnv *nativeEnvironment;
632         void *venv;
633     } UnionJNIEnvToVoid;
634 
635     __android_log_print(ANDROID_LOG_INFO, logTag, "Positioning start");
636     UnionJNIEnvToVoid uenv;
637     uenv.venv = nullptr;
638     javaVM = nullptr;
639 
640     if (vm->GetEnv(&uenv.venv, JNI_VERSION_1_6) != JNI_OK) {
641         __android_log_print(ANDROID_LOG_FATAL, logTag, "GetEnv failed");
642         return -1;
643     }
644     JNIEnv *env = uenv.nativeEnvironment;
645     if (!registerNatives(env)) {
646         __android_log_print(ANDROID_LOG_FATAL, logTag, "registerNatives failed");
647         return -1;
648     }
649 
650     javaVM = vm;
651     return JNI_VERSION_1_6;
652 }
653 
654