8#include <QtPositioning/QGeoPositionInfo>
9#include <QtCore/QDateTime>
11#include <QtCore/QRandomGenerator>
12#include <QtCore/QJniEnvironment>
13#include <QtCore/QJniObject>
14#include <QtCore/QLoggingCategory>
15#include <QtCore/QPermission>
16#include <QtCore/QCoreApplication>
17#include <QtCore/QTimeZone>
19#include <android/log.h>
22Q_DECLARE_JNI_CLASS(GnssStatus,
"android/location/GnssStatus")
23Q_DECLARE_JNI_CLASS(Location,
"android/location/Location")
25using namespace Qt::StringLiterals;
37 env->DeleteGlobalRef(m_classRef);
46 env->DeleteGlobalRef(m_classRef);
50 m_classRef = env.findClass<T>();
52 return m_classRef !=
nullptr;
58 jclass m_classRef =
nullptr;
70static const char logTag[] =
"qt.positioning.android";
78
79
80
81
82
83class ConstellationMapper
88 m_gnssStatusObject = QJniEnvironment().findClass<QtJniTypes::GnssStatus>();
89 if (!m_gnssStatusObject)
92 const auto sdkVersion = QNativeInterface::QAndroidApplication::sdkVersion();
93 if (sdkVersion >= 29) {
94 m_irnssTypeId = QJniObject::getStaticField<jint>(m_gnssStatusObject,
95 "CONSTELLATION_IRNSS");
100 static QGeoSatelliteInfo::SatelliteSystem toSatelliteSystem(
int constellationType)
102 if (!m_gnssStatusObject)
103 return QGeoSatelliteInfo::Undefined;
105 static const int gps =
106 QJniObject::getStaticField<jint>(m_gnssStatusObject,
"CONSTELLATION_GPS");
107 static const int glonass =
108 QJniObject::getStaticField<jint>(m_gnssStatusObject,
"CONSTELLATION_GLONASS");
109 static const int galileo =
110 QJniObject::getStaticField<jint>(m_gnssStatusObject,
"CONSTELLATION_GALILEO");
111 static const int beidou =
112 QJniObject::getStaticField<jint>(m_gnssStatusObject,
"CONSTELLATION_BEIDOU");
113 static const int qzss =
114 QJniObject::getStaticField<jint>(m_gnssStatusObject,
"CONSTELLATION_QZSS");
115 static const int sbas =
116 QJniObject::getStaticField<jint>(m_gnssStatusObject,
"CONSTELLATION_SBAS");
118 if (constellationType == gps) {
119 return QGeoSatelliteInfo::GPS;
120 }
else if (constellationType == glonass) {
121 return QGeoSatelliteInfo::GLONASS;
122 }
else if (constellationType == galileo) {
123 return QGeoSatelliteInfo::GALILEO;
124 }
else if (constellationType == beidou) {
125 return QGeoSatelliteInfo::BEIDOU;
126 }
else if (constellationType == qzss){
127 return QGeoSatelliteInfo::QZSS;
128 }
else if (constellationType == sbas) {
129 return QGeoSatelliteInfo::SBAS;
130 }
else if (m_irnssTypeId > 0 && constellationType == m_irnssTypeId) {
131 return QGeoSatelliteInfo::IRNSS;
133 qCWarning(lcPositioning) <<
"Unknown satellite system" << constellationType;
134 return QGeoSatelliteInfo::Undefined;
139 static jclass m_gnssStatusObject;
140 static int m_irnssTypeId;
143jclass ConstellationMapper::m_gnssStatusObject =
nullptr;
144int ConstellationMapper::m_irnssTypeId = 0;
167 }
else if (
obj->
inherits(
"QGeoSatelliteInfoSource")) {
199 QGeoPositionInfoSource::PositioningMethods ret = QGeoPositionInfoSource::NoPositioningMethods;
203 QJniObject jniProvidersObj =
204 QJniObject::callStaticMethod<jobject>(positioningClass(), providerListMethodId);
205 jintArray jProviders = jniProvidersObj.object<jintArray>();
208 __android_log_print(ANDROID_LOG_INFO,
logTag,
"Got null providers array!");
211 jint *providers = env->GetIntArrayElements(jProviders,
nullptr);
212 const int size = env->GetArrayLength(jProviders);
213 for (
int i = 0; i < size; i++) {
214 switch (providers[i]) {
216 ret |= QGeoPositionInfoSource::SatellitePositioningMethods;
218 case PROVIDER_NETWORK:
219 ret |= QGeoPositionInfoSource::NonSatellitePositioningMethods;
221 case PROVIDER_PASSIVE:
225 __android_log_print(ANDROID_LOG_INFO,
logTag,
"Unknown positioningMethod");
229 env->ReleaseIntArrayElements(jProviders, providers, 0);
236 QGeoPositionInfo info;
238 QJniObject jniObject(location);
239 if (!jniObject.isValid())
240 return QGeoPositionInfo();
242 const jdouble latitude = jniObject.callMethod<jdouble>(
"getLatitude");
243 const jdouble longitude = jniObject.callMethod<jdouble>(
"getLongitude");
245 QGeoCoordinate coordinate(latitude, longitude);
248 jboolean attributeExists = jniObject.callMethod<jboolean>(
"hasAltitude");
249 if (attributeExists) {
250 const jdouble value = jniObject.callMethod<jdouble>(
"getAltitude");
251 if (!qFuzzyIsNull(value))
252 coordinate.setAltitude(value);
259 if (useAltConverter && QNativeInterface::QAndroidApplication::sdkVersion() >= 34) {
260 attributeExists = jniObject.callMethod<jboolean>(
"hasMslAltitude");
261 if (attributeExists) {
262 const jdouble value = jniObject.callMethod<jdouble>(
"getMslAltitudeMeters");
263 if (!qFuzzyIsNull(value))
264 coordinate.setAltitude(value);
268 info.setCoordinate(coordinate);
271 const jlong timestamp = jniObject.callMethod<jlong>(
"getTime");
272 info.setTimestamp(QDateTime::fromMSecsSinceEpoch(timestamp, QTimeZone::UTC));
275 attributeExists = jniObject.callMethod<jboolean>(
"hasAccuracy");
276 if (attributeExists) {
277 const jfloat accuracy = jniObject.callMethod<jfloat>(
"getAccuracy");
278 if (!qFuzzyIsNull(accuracy))
279 info.setAttribute(QGeoPositionInfo::HorizontalAccuracy, qreal(accuracy));
283 attributeExists = jniObject.callMethod<jboolean>(
"hasVerticalAccuracy");
284 if (attributeExists) {
285 const jfloat accuracy = jniObject.callMethod<jfloat>(
"getVerticalAccuracyMeters");
286 if (!qFuzzyIsNull(accuracy))
287 info.setAttribute(QGeoPositionInfo::VerticalAccuracy, qreal(accuracy));
291 attributeExists = jniObject.callMethod<jboolean>(
"hasSpeed");
292 if (attributeExists) {
293 const jfloat speed = jniObject.callMethod<jfloat>(
"getSpeed");
294 if (!qFuzzyIsNull(speed))
295 info.setAttribute(QGeoPositionInfo::GroundSpeed, qreal(speed));
299 attributeExists = jniObject.callMethod<jboolean>(
"hasBearing");
300 if (attributeExists) {
301 const jfloat bearing = jniObject.callMethod<jfloat>(
"getBearing");
302 if (!qFuzzyIsNull(bearing))
303 info.setAttribute(QGeoPositionInfo::Direction, qreal(bearing));
306 const jfloat bearingAccuracy =
307 jniObject.callMethod<jfloat>(
"getBearingAccuracyDegrees");
308 if (!qFuzzyIsNull(bearingAccuracy))
309 info.setAttribute(QGeoPositionInfo::DirectionAccuracy, qreal(bearingAccuracy));
318 return std::make_pair(
static_cast<
int>(info.satelliteSystem()),
319 info.satelliteIdentifier());
323 QList<QGeoSatelliteInfo>* usedInFix)
325 QJniObject jniStatus(gnssStatus);
326 QList<QGeoSatelliteInfo> sats;
329 const int satellitesCount = jniStatus.callMethod<jint>(
"getSatelliteCount");
330 for (
int i = 0; i < satellitesCount; ++i) {
331 QGeoSatelliteInfo info;
336 const jfloat cn0 = jniStatus.callMethod<jfloat>(
"getCn0DbHz", i);
337 info.setSignalStrength(
static_cast<
int>(cn0));
340 const jint constellationType =
341 jniStatus.callMethod<jint>(
"getConstellationType", i);
342 info.setSatelliteSystem(ConstellationMapper::toSatelliteSystem(constellationType));
345 const jint svId = jniStatus.callMethod<jint>(
"getSvid", i);
346 info.setSatelliteIdentifier(svId);
349 const jfloat azimuth = jniStatus.callMethod<jfloat>(
"getAzimuthDegrees", i);
350 info.setAttribute(QGeoSatelliteInfo::Azimuth,
static_cast<qreal>(azimuth));
353 const jfloat elevation = jniStatus.callMethod<jfloat>(
"getElevationDegrees", i);
354 info.setAttribute(QGeoSatelliteInfo::Elevation,
static_cast<qreal>(elevation));
358 const jboolean inFix = jniStatus.callMethod<jboolean>(
"usedInFix", i);
360 const UniqueId id = getUid(info);
361 if (uids.contains(id))
368 usedInFix->append(info);
375 bool useAltitudeConverter)
379 return QGeoPositionInfo();
381 const auto accuracy = fromSatellitePositioningMethodsOnly
385 if (!hasPositioningPermissions(accuracy))
388 QJniObject locationObj = QJniObject::callStaticMethod<jobject>(
389 positioningClass(), lastKnownPositionMethodId, fromSatellitePositioningMethodsOnly,
390 useAltitudeConverter);
391 jobject location = locationObj.object();
392 if (location ==
nullptr)
393 return QGeoPositionInfo();
395 const QGeoPositionInfo info = positionInfoFromJavaLocation(location, useAltitudeConverter);
402 int providerSelection = 0;
403 if (m & QGeoPositionInfoSource::SatellitePositioningMethods)
404 providerSelection |= 1;
405 if (m & QGeoPositionInfoSource::NonSatellitePositioningMethods)
406 providerSelection |= 2;
408 return providerSelection;
415 if (m & QGeoPositionInfoSource::NonSatellitePositioningMethods)
417 if (m & QGeoPositionInfoSource::SatellitePositioningMethods)
426 return QGeoPositionInfoSource::UnknownSourceError;
431 const auto preferredMethods = source->preferredPositioningMethods();
432 const auto accuracy = accuracyFromPositioningMethods(preferredMethods);
433 if (!hasPositioningPermissions(accuracy))
434 return QGeoPositionInfoSource::AccessError;
436 int errorCode = QJniObject::callStaticMethod<jint>(
437 positioningClass(), startUpdatesMethodId, androidClassKey,
438 positioningMethodToInt(preferredMethods),
439 source->updateInterval(), source->useAltitudeConverter());
445 return static_cast<QGeoPositionInfoSource::Error>(errorCode);
451 return QGeoPositionInfoSource::UnknownSourceError;
457 QJniObject::callStaticMethod<
void>(positioningClass(), stopUpdatesMethodId,
465 return QGeoPositionInfoSource::UnknownSourceError;
470 const auto preferredMethods = source->preferredPositioningMethods();
471 const auto accuracy = accuracyFromPositioningMethods(preferredMethods);
472 if (!hasPositioningPermissions(accuracy))
473 return QGeoPositionInfoSource::AccessError;
475 int errorCode = QJniObject::callStaticMethod<jint>(
476 positioningClass(), requestUpdateMethodId, androidClassKey,
477 positioningMethodToInt(preferredMethods),
478 timeout, source->useAltitudeConverter());
484 return static_cast<QGeoPositionInfoSource::Error>(errorCode);
489 return QGeoPositionInfoSource::UnknownSourceError;
496 return QGeoSatelliteInfoSource::UnknownSourceError;
503 if (!hasPositioningPermissions(AccuracyType::Precise))
504 return QGeoSatelliteInfoSource::AccessError;
506 int interval = source->updateInterval();
508 interval = requestTimeout;
509 int errorCode = QJniObject::callStaticMethod<jint>(positioningClass(),
510 startSatelliteUpdatesMethodId,
511 androidClassKey, interval,
518 return static_cast<QGeoSatelliteInfoSource::Error>(errorCode);
520 qCWarning(lcPositioning)
521 <<
"startSatelliteUpdates: Unknown error code" << errorCode;
525 return QGeoSatelliteInfoSource::UnknownSourceError;
531 QLocationPermission permission;
534 if (!QNativeInterface::QAndroidApplication::isActivityContext())
535 permission.setAvailability(QLocationPermission::Always);
537 bool permitted =
false;
539 permission.setAccuracy(QLocationPermission::Precise);
540 permitted = qApp->checkPermission(permission) == Qt::PermissionStatus::Granted;
543 permission.setAccuracy(QLocationPermission::Approximate);
544 permitted |= qApp->checkPermission(permission) == Qt::PermissionStatus::Granted;
548 qCWarning(lcPositioning) <<
"Position data not available due to missing permission";
555 jint androidClassKey, jboolean isSingleUpdate)
562 qCWarning(lcPositioning) <<
"positionUpdated: source == 0";
567 QGeoPositionInfo info =
568 AndroidPositioning::positionInfoFromJavaLocation(location.object(), useAltitudeConverter);
572 QMetaObject::invokeMethod(source,
"processPositionUpdate", Qt::AutoConnection,
573 Q_ARG(QGeoPositionInfo, info));
575 QMetaObject::invokeMethod(source,
"processSinglePositionUpdate", Qt::AutoConnection,
576 Q_ARG(QGeoPositionInfo, info));
578Q_DECLARE_JNI_NATIVE_METHOD(positionUpdated)
584 QObject *source = AndroidPositioning::idToPosSource()->value(androidClassKey);
586 source = AndroidPositioning::idToSatSource()->value(androidClassKey);
588 qCWarning(lcPositioning) <<
"locationProvidersDisabled: source == 0";
592 QMetaObject::invokeMethod(source,
"locationProviderDisabled", Qt::AutoConnection);
594Q_DECLARE_JNI_NATIVE_METHOD(locationProvidersDisabled)
600 QObject *source = AndroidPositioning::idToPosSource()->value(androidClassKey);
602 qCWarning(lcPositioning) <<
"locationProvidersChanged: source == 0";
606 QMetaObject::invokeMethod(source,
"locationProvidersChanged", Qt::AutoConnection);
608Q_DECLARE_JNI_NATIVE_METHOD(locationProvidersChanged)
611 const QList<QGeoSatelliteInfo> &inUse,
612 jint androidClassKey, jboolean isSingleUpdate)
616 qCWarning(lcPositioning) <<
"notifySatelliteInfoUpdated: source == 0";
620 QMetaObject::invokeMethod(source,
"processSatelliteUpdate", Qt::AutoConnection,
621 Q_ARG(QList<QGeoSatelliteInfo>, inView),
622 Q_ARG(QList<QGeoSatelliteInfo>, inUse),
623 Q_ARG(
bool, isSingleUpdate));
627 jint androidClassKey, jboolean isSingleUpdate)
632 QList<QGeoSatelliteInfo> inUse;
633 QList<QGeoSatelliteInfo> sats =
634 AndroidPositioning::satelliteInfoFromJavaGnssStatus(gnssStatus.object(), &inUse);
636 notifySatelliteInfoUpdated(sats, inUse, androidClassKey, isSingleUpdate);
638Q_DECLARE_JNI_NATIVE_METHOD(satelliteGnssUpdated)
640#define GET_AND_CHECK_STATIC_METHOD(VAR, METHOD_NAME, ...)
641 VAR = env.findStaticMethod<__VA_ARGS__>(positioningClass(), METHOD_NAME);
643 __android_log_print(ANDROID_LOG_FATAL, logTag, methodErrorMsg, METHOD_NAME,
644 QtJniTypes::methodSignature<__VA_ARGS__>().data());
652 __android_log_print(ANDROID_LOG_FATAL,
logTag,
"Failed to create environment");
656 if (!positioningClass.init()) {
657 __android_log_print(ANDROID_LOG_FATAL,
logTag,
"Failed to create global class ref");
661 if (!env.registerNativeMethods(positioningClass(), {
662 Q_JNI_NATIVE_METHOD(positionUpdated),
663 Q_JNI_NATIVE_METHOD(locationProvidersDisabled),
664 Q_JNI_NATIVE_METHOD(locationProvidersChanged),
665 Q_JNI_NATIVE_METHOD(satelliteGnssUpdated)
667 __android_log_print(ANDROID_LOG_FATAL,
logTag,
"Failed to register native methods");
673 QtJniTypes::Location,
bool,
bool);
680 jint, jint, jint,
bool);
685extern "C" Q_DECL_EXPORT jint JNICALL JNI_OnLoad(JavaVM * ,
void * )
687 static bool initialized =
false;
689 return JNI_VERSION_1_6;
692 __android_log_print(ANDROID_LOG_INFO, logTag,
"Positioning start");
694 const auto context = QNativeInterface::QAndroidApplication::context();
695 QtJniTypes::QtPositioning::callStaticMethod<
void>(
"setContext", context);
697 if (!registerNatives()) {
698 __android_log_print(ANDROID_LOG_FATAL, logTag,
"registerNatives() failed");
702 if (!ConstellationMapper::init()) {
703 __android_log_print(ANDROID_LOG_ERROR, logTag,
704 "Failed to extract constellation type constants. "
705 "Satellite system will be undefined!");
708 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 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)
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")