7#include <QtPositioning/QGeoPositionInfo>
8#include <QtCore/QDateTime>
10#include <QtCore/QRandomGenerator>
11#include <QtCore/QJniEnvironment>
12#include <QtCore/QJniObject>
13#include <QtCore/QLoggingCategory>
14#include <QtCore/QPermission>
15#include <QtCore/QCoreApplication>
16#include <QtCore/QTimeZone>
18#include <android/log.h>
21Q_DECLARE_JNI_CLASS(GnssStatus,
"android/location/GnssStatus")
22Q_DECLARE_JNI_CLASS(Location,
"android/location/Location")
24using namespace Qt::StringLiterals;
36 env->DeleteGlobalRef(m_classRef);
45 env->DeleteGlobalRef(m_classRef);
49 m_classRef = env.findClass<T>();
51 return m_classRef !=
nullptr;
57 jclass m_classRef =
nullptr;
69static const char logTag[] =
"qt.positioning.android";
77
78
79
80
81
82class ConstellationMapper
87 m_gnssStatusObject =
nullptr;
88 if (QNativeInterface::QAndroidApplication::sdkVersion() > 23) {
89 m_gnssStatusObject = QJniEnvironment().findClass<QtJniTypes::GnssStatus>();
90 if (!m_gnssStatusObject)
97 static QGeoSatelliteInfo::SatelliteSystem toSatelliteSystem(
int constellationType)
99 if (!m_gnssStatusObject)
100 return QGeoSatelliteInfo::Undefined;
102 static const int gps =
103 QJniObject::getStaticField<jint>(m_gnssStatusObject,
"CONSTELLATION_GPS");
104 static const int glonass =
105 QJniObject::getStaticField<jint>(m_gnssStatusObject,
"CONSTELLATION_GLONASS");
106 static const int galileo =
107 QJniObject::getStaticField<jint>(m_gnssStatusObject,
"CONSTELLATION_GALILEO");
108 static const int beidou =
109 QJniObject::getStaticField<jint>(m_gnssStatusObject,
"CONSTELLATION_BEIDOU");
110 static const int qzss =
111 QJniObject::getStaticField<jint>(m_gnssStatusObject,
"CONSTELLATION_QZSS");
113 if (constellationType == gps) {
114 return QGeoSatelliteInfo::GPS;
115 }
else if (constellationType == glonass) {
116 return QGeoSatelliteInfo::GLONASS;
117 }
else if (constellationType == galileo) {
118 return QGeoSatelliteInfo::GALILEO;
119 }
else if (constellationType == beidou) {
120 return QGeoSatelliteInfo::BEIDOU;
121 }
else if (constellationType == qzss){
122 return QGeoSatelliteInfo::QZSS;
124 qCWarning(lcPositioning) <<
"Unknown satellite system" << constellationType;
125 return QGeoSatelliteInfo::Undefined;
130 static jclass m_gnssStatusObject;
133jclass ConstellationMapper::m_gnssStatusObject =
nullptr;
156 }
else if (
obj->
inherits(
"QGeoSatelliteInfoSource")) {
188 QGeoPositionInfoSource::PositioningMethods ret = QGeoPositionInfoSource::NoPositioningMethods;
192 QJniObject jniProvidersObj =
193 QJniObject::callStaticMethod<jobject>(positioningClass(), providerListMethodId);
194 jintArray jProviders = jniProvidersObj.object<jintArray>();
197 __android_log_print(ANDROID_LOG_INFO, logTag,
"Got null providers array!");
200 jint *providers = env->GetIntArrayElements(jProviders,
nullptr);
201 const int size = env->GetArrayLength(jProviders);
202 for (
int i = 0; i < size; i++) {
203 switch (providers[i]) {
205 ret |= QGeoPositionInfoSource::SatellitePositioningMethods;
207 case PROVIDER_NETWORK:
208 ret |= QGeoPositionInfoSource::NonSatellitePositioningMethods;
210 case PROVIDER_PASSIVE:
214 __android_log_print(ANDROID_LOG_INFO, logTag,
"Unknown positioningMethod");
218 env->ReleaseIntArrayElements(jProviders, providers, 0);
225 QGeoPositionInfo info;
227 QJniObject jniObject(location);
228 if (!jniObject.isValid())
229 return QGeoPositionInfo();
231 const jdouble latitude = jniObject.callMethod<jdouble>(
"getLatitude");
232 const jdouble longitude = jniObject.callMethod<jdouble>(
"getLongitude");
234 QGeoCoordinate coordinate(latitude, longitude);
237 jboolean attributeExists = jniObject.callMethod<jboolean>(
"hasAltitude");
238 if (attributeExists) {
239 const jdouble value = jniObject.callMethod<jdouble>(
"getAltitude");
240 if (!qFuzzyIsNull(value))
241 coordinate.setAltitude(value);
248 if (useAltConverter && QNativeInterface::QAndroidApplication::sdkVersion() >= 34) {
249 attributeExists = jniObject.callMethod<jboolean>(
"hasMslAltitude");
250 if (attributeExists) {
251 const jdouble value = jniObject.callMethod<jdouble>(
"getMslAltitudeMeters");
252 if (!qFuzzyIsNull(value))
253 coordinate.setAltitude(value);
257 info.setCoordinate(coordinate);
260 const jlong timestamp = jniObject.callMethod<jlong>(
"getTime");
261 info.setTimestamp(QDateTime::fromMSecsSinceEpoch(timestamp, QTimeZone::UTC));
264 attributeExists = jniObject.callMethod<jboolean>(
"hasAccuracy");
265 if (attributeExists) {
266 const jfloat accuracy = jniObject.callMethod<jfloat>(
"getAccuracy");
267 if (!qFuzzyIsNull(accuracy))
268 info.setAttribute(QGeoPositionInfo::HorizontalAccuracy, qreal(accuracy));
272 if (QNativeInterface::QAndroidApplication::sdkVersion() > 25) {
273 attributeExists = jniObject.callMethod<jboolean>(
"hasVerticalAccuracy");
274 if (attributeExists) {
275 const jfloat accuracy = jniObject.callMethod<jfloat>(
"getVerticalAccuracyMeters");
276 if (!qFuzzyIsNull(accuracy))
277 info.setAttribute(QGeoPositionInfo::VerticalAccuracy, qreal(accuracy));
282 attributeExists = jniObject.callMethod<jboolean>(
"hasSpeed");
283 if (attributeExists) {
284 const jfloat speed = jniObject.callMethod<jfloat>(
"getSpeed");
285 if (!qFuzzyIsNull(speed))
286 info.setAttribute(QGeoPositionInfo::GroundSpeed, qreal(speed));
290 attributeExists = jniObject.callMethod<jboolean>(
"hasBearing");
291 if (attributeExists) {
292 const jfloat bearing = jniObject.callMethod<jfloat>(
"getBearing");
293 if (!qFuzzyIsNull(bearing))
294 info.setAttribute(QGeoPositionInfo::Direction, qreal(bearing));
297 if (QNativeInterface::QAndroidApplication::sdkVersion() > 25) {
298 const jfloat bearingAccuracy =
299 jniObject.callMethod<jfloat>(
"getBearingAccuracyDegrees");
300 if (!qFuzzyIsNull(bearingAccuracy))
301 info.setAttribute(QGeoPositionInfo::DirectionAccuracy, qreal(bearingAccuracy));
311 return std::make_pair(
static_cast<
int>(info.satelliteSystem()),
312 info.satelliteIdentifier());
316 jobjectArray satellites,
317 QList<QGeoSatelliteInfo>* usedInFix)
320 QList<QGeoSatelliteInfo> sats;
321 jsize length = jniEnv->GetArrayLength(satellites);
322 for (
int i = 0; i<length; i++) {
323 jobject element = jniEnv->GetObjectArrayElement(satellites, i);
324 if (QJniEnvironment::checkAndClearExceptions(jniEnv)) {
325 qCWarning(lcPositioning) <<
"Cannot process all satellite data due to exception.";
329 QJniObject jniObj = QJniObject::fromLocalRef(element);
330 if (!jniObj.isValid())
333 QGeoSatelliteInfo info;
336 const jfloat snr = jniObj.callMethod<jfloat>(
"getSnr");
337 info.setSignalStrength(
int(snr));
340 if (qFuzzyIsNull(snr))
344 const jint prn = jniObj.callMethod<jint>(
"getPrn");
345 info.setSatelliteIdentifier(prn);
347 if (prn >= 1 && prn <= 32)
348 info.setSatelliteSystem(QGeoSatelliteInfo::GPS);
349 else if (prn >= 65 && prn <= 96)
350 info.setSatelliteSystem(QGeoSatelliteInfo::GLONASS);
351 else if (prn >= 193 && prn <= 200)
352 info.setSatelliteSystem(QGeoSatelliteInfo::QZSS);
353 else if ((prn >= 201 && prn <= 235) || (prn >= 401 && prn <= 437))
354 info.setSatelliteSystem(QGeoSatelliteInfo::BEIDOU);
355 else if (prn >= 301 && prn <= 336)
356 info.setSatelliteSystem(QGeoSatelliteInfo::GALILEO);
359 const jfloat azimuth = jniObj.callMethod<jfloat>(
"getAzimuth");
360 info.setAttribute(QGeoSatelliteInfo::Azimuth, qreal(azimuth));
363 const jfloat elevation = jniObj.callMethod<jfloat>(
"getElevation");
364 info.setAttribute(QGeoSatelliteInfo::Elevation, qreal(elevation));
368 const jboolean inFix = jniObj.callMethod<jboolean>(
"usedInFix");
370 const UniqueId id = getUid(info);
371 if (uids.contains(id))
378 usedInFix->append(info);
385 QList<QGeoSatelliteInfo>* usedInFix)
387 QJniObject jniStatus(gnssStatus);
388 QList<QGeoSatelliteInfo> sats;
391 const int satellitesCount = jniStatus.callMethod<jint>(
"getSatelliteCount");
392 for (
int i = 0; i < satellitesCount; ++i) {
393 QGeoSatelliteInfo info;
398 const jfloat cn0 = jniStatus.callMethod<jfloat>(
"getCn0DbHz", i);
399 info.setSignalStrength(
static_cast<
int>(cn0));
402 const jint constellationType =
403 jniStatus.callMethod<jint>(
"getConstellationType", i);
404 info.setSatelliteSystem(ConstellationMapper::toSatelliteSystem(constellationType));
407 const jint svId = jniStatus.callMethod<jint>(
"getSvid", i);
408 info.setSatelliteIdentifier(svId);
411 const jfloat azimuth = jniStatus.callMethod<jfloat>(
"getAzimuthDegrees", i);
412 info.setAttribute(QGeoSatelliteInfo::Azimuth,
static_cast<qreal>(azimuth));
415 const jfloat elevation = jniStatus.callMethod<jfloat>(
"getElevationDegrees", i);
416 info.setAttribute(QGeoSatelliteInfo::Elevation,
static_cast<qreal>(elevation));
420 const jboolean inFix = jniStatus.callMethod<jboolean>(
"usedInFix", i);
422 const UniqueId id = getUid(info);
423 if (uids.contains(id))
430 usedInFix->append(info);
437 bool useAltitudeConverter)
441 return QGeoPositionInfo();
443 const auto accuracy = fromSatellitePositioningMethodsOnly
447 if (!hasPositioningPermissions(accuracy))
450 QJniObject locationObj = QJniObject::callStaticMethod<jobject>(
451 positioningClass(), lastKnownPositionMethodId, fromSatellitePositioningMethodsOnly,
452 useAltitudeConverter);
453 jobject location = locationObj.object();
454 if (location ==
nullptr)
455 return QGeoPositionInfo();
457 const QGeoPositionInfo info = positionInfoFromJavaLocation(location, useAltitudeConverter);
464 int providerSelection = 0;
465 if (m & QGeoPositionInfoSource::SatellitePositioningMethods)
466 providerSelection |= 1;
467 if (m & QGeoPositionInfoSource::NonSatellitePositioningMethods)
468 providerSelection |= 2;
470 return providerSelection;
477 if (m & QGeoPositionInfoSource::NonSatellitePositioningMethods)
479 if (m & QGeoPositionInfoSource::SatellitePositioningMethods)
488 return QGeoPositionInfoSource::UnknownSourceError;
493 const auto preferredMethods = source->preferredPositioningMethods();
494 const auto accuracy = accuracyFromPositioningMethods(preferredMethods);
495 if (!hasPositioningPermissions(accuracy))
496 return QGeoPositionInfoSource::AccessError;
498 int errorCode = QJniObject::callStaticMethod<jint>(
499 positioningClass(), startUpdatesMethodId, androidClassKey,
500 positioningMethodToInt(preferredMethods),
501 source->updateInterval(), source->useAltitudeConverter());
507 return static_cast<QGeoPositionInfoSource::Error>(errorCode);
513 return QGeoPositionInfoSource::UnknownSourceError;
519 QJniObject::callStaticMethod<
void>(positioningClass(), stopUpdatesMethodId,
527 return QGeoPositionInfoSource::UnknownSourceError;
532 const auto preferredMethods = source->preferredPositioningMethods();
533 const auto accuracy = accuracyFromPositioningMethods(preferredMethods);
534 if (!hasPositioningPermissions(accuracy))
535 return QGeoPositionInfoSource::AccessError;
537 int errorCode = QJniObject::callStaticMethod<jint>(
538 positioningClass(), requestUpdateMethodId, androidClassKey,
539 positioningMethodToInt(preferredMethods),
540 timeout, source->useAltitudeConverter());
546 return static_cast<QGeoPositionInfoSource::Error>(errorCode);
551 return QGeoPositionInfoSource::UnknownSourceError;
558 return QGeoSatelliteInfoSource::UnknownSourceError;
565 if (!hasPositioningPermissions(AccuracyType::Precise))
566 return QGeoSatelliteInfoSource::AccessError;
568 int interval = source->updateInterval();
570 interval = requestTimeout;
571 int errorCode = QJniObject::callStaticMethod<jint>(positioningClass(),
572 startSatelliteUpdatesMethodId,
573 androidClassKey, interval,
580 return static_cast<QGeoSatelliteInfoSource::Error>(errorCode);
582 qCWarning(lcPositioning)
583 <<
"startSatelliteUpdates: Unknown error code" << errorCode;
587 return QGeoSatelliteInfoSource::UnknownSourceError;
593 QLocationPermission permission;
596 if (!QNativeInterface::QAndroidApplication::isActivityContext())
597 permission.setAvailability(QLocationPermission::Always);
599 bool permitted =
false;
601 permission.setAccuracy(QLocationPermission::Precise);
602 permitted = qApp->checkPermission(permission) == Qt::PermissionStatus::Granted;
605 permission.setAccuracy(QLocationPermission::Approximate);
606 permitted |= qApp->checkPermission(permission) == Qt::PermissionStatus::Granted;
610 qCWarning(lcPositioning) <<
"Position data not available due to missing permission";
617 jint androidClassKey, jboolean isSingleUpdate)
624 qCWarning(lcPositioning) <<
"positionUpdated: source == 0";
629 QGeoPositionInfo info =
630 AndroidPositioning::positionInfoFromJavaLocation(location.object(), useAltitudeConverter);
634 QMetaObject::invokeMethod(source,
"processPositionUpdate", Qt::AutoConnection,
635 Q_ARG(QGeoPositionInfo, info));
637 QMetaObject::invokeMethod(source,
"processSinglePositionUpdate", Qt::AutoConnection,
638 Q_ARG(QGeoPositionInfo, info));
640Q_DECLARE_JNI_NATIVE_METHOD(positionUpdated)
646 QObject *source = AndroidPositioning::idToPosSource()->value(androidClassKey);
648 source = AndroidPositioning::idToSatSource()->value(androidClassKey);
650 qCWarning(lcPositioning) <<
"locationProvidersDisabled: source == 0";
654 QMetaObject::invokeMethod(source,
"locationProviderDisabled", Qt::AutoConnection);
656Q_DECLARE_JNI_NATIVE_METHOD(locationProvidersDisabled)
662 QObject *source = AndroidPositioning::idToPosSource()->value(androidClassKey);
664 qCWarning(lcPositioning) <<
"locationProvidersChanged: source == 0";
668 QMetaObject::invokeMethod(source,
"locationProvidersChanged", Qt::AutoConnection);
670Q_DECLARE_JNI_NATIVE_METHOD(locationProvidersChanged)
673 const QList<QGeoSatelliteInfo> &inUse,
674 jint androidClassKey, jboolean isSingleUpdate)
678 qCWarning(lcPositioning) <<
"notifySatelliteInfoUpdated: source == 0";
682 QMetaObject::invokeMethod(source,
"processSatelliteUpdate", Qt::AutoConnection,
683 Q_ARG(QList<QGeoSatelliteInfo>, inView),
684 Q_ARG(QList<QGeoSatelliteInfo>, inUse),
685 Q_ARG(
bool, isSingleUpdate));
689 jobjectArray satellites,
690 jint androidClassKey, jboolean isSingleUpdate)
693 QList<QGeoSatelliteInfo> inUse;
694 QList<QGeoSatelliteInfo> sats =
695 AndroidPositioning::satelliteInfoFromJavaLocation(env, satellites, &inUse);
697 notifySatelliteInfoUpdated(sats, inUse, androidClassKey, isSingleUpdate);
699Q_DECLARE_JNI_NATIVE_METHOD(satelliteGpsUpdated)
702 jint androidClassKey, jboolean isSingleUpdate)
707 QList<QGeoSatelliteInfo> inUse;
708 QList<QGeoSatelliteInfo> sats =
709 AndroidPositioning::satelliteInfoFromJavaGnssStatus(gnssStatus.object(), &inUse);
711 notifySatelliteInfoUpdated(sats, inUse, androidClassKey, isSingleUpdate);
713Q_DECLARE_JNI_NATIVE_METHOD(satelliteGnssUpdated)
715#define GET_AND_CHECK_STATIC_METHOD(VAR, METHOD_NAME, ...)
716 VAR = env.findStaticMethod<__VA_ARGS__>(positioningClass(), METHOD_NAME);
718 __android_log_print(ANDROID_LOG_FATAL, logTag, methodErrorMsg, METHOD_NAME,
719 QtJniTypes::methodSignature<__VA_ARGS__>().data());
727 __android_log_print(ANDROID_LOG_FATAL, logTag,
"Failed to create environment");
731 if (!positioningClass.init()) {
732 __android_log_print(ANDROID_LOG_FATAL, logTag,
"Failed to create global class ref");
736 if (!env.registerNativeMethods(positioningClass(), {
737 Q_JNI_NATIVE_METHOD(positionUpdated),
738 Q_JNI_NATIVE_METHOD(locationProvidersDisabled),
739 Q_JNI_NATIVE_METHOD(satelliteGpsUpdated),
740 Q_JNI_NATIVE_METHOD(locationProvidersChanged),
741 Q_JNI_NATIVE_METHOD(satelliteGnssUpdated)
743 __android_log_print(ANDROID_LOG_FATAL, logTag,
"Failed to register native methods");
749 QtJniTypes::Location,
bool,
bool);
756 jint, jint, jint,
bool);
761Q_DECL_EXPORT jint JNICALL JNI_OnLoad(JavaVM * ,
void * )
763 static bool initialized =
false;
765 return JNI_VERSION_1_6;
768 __android_log_print(ANDROID_LOG_INFO, logTag,
"Positioning start");
770 const auto context = QNativeInterface::QAndroidApplication::context();
771 QtJniTypes::QtPositioning::callStaticMethod<
void>(
"setContext", context);
773 if (!registerNatives()) {
774 __android_log_print(ANDROID_LOG_FATAL, logTag,
"registerNatives() failed");
778 if (!ConstellationMapper::init()) {
779 __android_log_print(ANDROID_LOG_ERROR, logTag,
780 "Failed to extract constellation type constants. "
781 "Satellite system will be undefined!");
784 return JNI_VERSION_1_6;
#define GET_AND_CHECK_STATIC_METHOD(VAR, CLASS, METHOD_NAME, METHOD_SIGNATURE)
GlobalClassRefWrapper()=default
bool useAltitudeConverter() const
static jmethodID startUpdatesMethodId
static void positionUpdated(JNIEnv *env, jobject thiz, QtJniTypes::Location location, jint androidClassKey, jboolean isSingleUpdate)
static jmethodID requestUpdateMethodId
static void notifySatelliteInfoUpdated(const QList< QGeoSatelliteInfo > &inView, const QList< QGeoSatelliteInfo > &inUse, jint androidClassKey, jboolean isSingleUpdate)
static jmethodID providerListMethodId
static jmethodID lastKnownPositionMethodId
static void satelliteGpsUpdated(JNIEnv *env, jobject thiz, jobjectArray satellites, jint androidClassKey, jboolean isSingleUpdate)
static GlobalClassRefWrapper< QtJniTypes::QtPositioning > positioningClass
static const char logTag[]
static void satelliteGnssUpdated(JNIEnv *env, jobject thiz, QtJniTypes::GnssStatus gnssStatus, jint androidClassKey, jboolean isSingleUpdate)
static void locationProvidersChanged(JNIEnv *env, jobject thiz, jint androidClassKey)
static jmethodID stopUpdatesMethodId
static const char methodErrorMsg[]
static void locationProvidersDisabled(JNIEnv *env, jobject thiz, jint androidClassKey)
static bool registerNatives()
static jmethodID startSatelliteUpdatesMethodId
static UniqueId getUid(const QGeoSatelliteInfo &info)
QList< QGeoSatelliteInfo > satelliteInfoFromJavaGnssStatus(jobject gnssStatus, QList< QGeoSatelliteInfo > *usedInFix)
QMap< int, QGeoSatelliteInfoSourceAndroid * > SatelliteSourceMap
int positioningMethodToInt(QGeoPositionInfoSource::PositioningMethods m)
QGeoSatelliteInfoSource::Error startSatelliteUpdates(int androidClassKey, bool isSingleRequest, int requestTimeout)
QList< QGeoSatelliteInfo > satelliteInfoFromJavaLocation(JNIEnv *jniEnv, jobjectArray satellites, QList< QGeoSatelliteInfo > *usedInFix)
QGeoPositionInfoSource::Error requestUpdate(int androidClassKey, int timeout)
QGeoPositionInfo lastKnownPosition(bool fromSatellitePositioningMethodsOnly, bool useAltitudeConverter)
QGeoPositionInfoSource::Error startUpdates(int androidClassKey)
static AccuracyTypes accuracyFromPositioningMethods(QGeoPositionInfoSource::PositioningMethods m)
QMap< int, QGeoPositionInfoSourceAndroid * > PositionSourceMap
QGeoPositionInfoSource::PositioningMethods availableProviders()
QGeoPositionInfo positionInfoFromJavaLocation(const jobject &location, bool useAltConverter)
void stopUpdates(int androidClassKey)
bool hasPositioningPermissions(AccuracyTypes accuracy)
Q_LOGGING_CATEGORY(lcEventDispatcher, "qt.eventdispatcher")
Q_DECLARE_JNI_CLASS(MotionEvent, "android/view/MotionEvent")