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 = QJniEnvironment().findClass<QtJniTypes::GnssStatus>();
88 if (!m_gnssStatusObject)
91 const auto sdkVersion = QNativeInterface::QAndroidApplication::sdkVersion();
92 if (sdkVersion >= 29) {
93 m_irnssTypeId = QJniObject::getStaticField<jint>(m_gnssStatusObject,
94 "CONSTELLATION_IRNSS");
99 static QGeoSatelliteInfo::SatelliteSystem toSatelliteSystem(
int constellationType)
101 if (!m_gnssStatusObject)
102 return QGeoSatelliteInfo::Undefined;
104 static const int gps =
105 QJniObject::getStaticField<jint>(m_gnssStatusObject,
"CONSTELLATION_GPS");
106 static const int glonass =
107 QJniObject::getStaticField<jint>(m_gnssStatusObject,
"CONSTELLATION_GLONASS");
108 static const int galileo =
109 QJniObject::getStaticField<jint>(m_gnssStatusObject,
"CONSTELLATION_GALILEO");
110 static const int beidou =
111 QJniObject::getStaticField<jint>(m_gnssStatusObject,
"CONSTELLATION_BEIDOU");
112 static const int qzss =
113 QJniObject::getStaticField<jint>(m_gnssStatusObject,
"CONSTELLATION_QZSS");
114 static const int sbas =
115 QJniObject::getStaticField<jint>(m_gnssStatusObject,
"CONSTELLATION_SBAS");
117 if (constellationType == gps) {
118 return QGeoSatelliteInfo::GPS;
119 }
else if (constellationType == glonass) {
120 return QGeoSatelliteInfo::GLONASS;
121 }
else if (constellationType == galileo) {
122 return QGeoSatelliteInfo::GALILEO;
123 }
else if (constellationType == beidou) {
124 return QGeoSatelliteInfo::BEIDOU;
125 }
else if (constellationType == qzss){
126 return QGeoSatelliteInfo::QZSS;
127 }
else if (constellationType == sbas) {
128 return QGeoSatelliteInfo::SBAS;
129 }
else if (m_irnssTypeId > 0 && constellationType == m_irnssTypeId) {
130 return QGeoSatelliteInfo::IRNSS;
132 qCWarning(lcPositioning) <<
"Unknown satellite system" << constellationType;
133 return QGeoSatelliteInfo::Undefined;
138 static jclass m_gnssStatusObject;
139 static int m_irnssTypeId;
142jclass ConstellationMapper::m_gnssStatusObject =
nullptr;
143int ConstellationMapper::m_irnssTypeId = 0;
166 }
else if (
obj->
inherits(
"QGeoSatelliteInfoSource")) {
198 QGeoPositionInfoSource::PositioningMethods ret = QGeoPositionInfoSource::NoPositioningMethods;
202 QJniObject jniProvidersObj =
203 QJniObject::callStaticMethod<jobject>(positioningClass(), providerListMethodId);
204 jintArray jProviders = jniProvidersObj.object<jintArray>();
207 __android_log_print(ANDROID_LOG_INFO,
logTag,
"Got null providers array!");
210 jint *providers = env->GetIntArrayElements(jProviders,
nullptr);
211 const int size = env->GetArrayLength(jProviders);
212 for (
int i = 0; i < size; i++) {
213 switch (providers[i]) {
215 ret |= QGeoPositionInfoSource::SatellitePositioningMethods;
217 case PROVIDER_NETWORK:
218 ret |= QGeoPositionInfoSource::NonSatellitePositioningMethods;
220 case PROVIDER_PASSIVE:
224 __android_log_print(ANDROID_LOG_INFO,
logTag,
"Unknown positioningMethod");
228 env->ReleaseIntArrayElements(jProviders, providers, 0);
235 QGeoPositionInfo info;
237 QJniObject jniObject(location);
238 if (!jniObject.isValid())
239 return QGeoPositionInfo();
241 const jdouble latitude = jniObject.callMethod<jdouble>(
"getLatitude");
242 const jdouble longitude = jniObject.callMethod<jdouble>(
"getLongitude");
244 QGeoCoordinate coordinate(latitude, longitude);
247 jboolean attributeExists = jniObject.callMethod<jboolean>(
"hasAltitude");
248 if (attributeExists) {
249 const jdouble value = jniObject.callMethod<jdouble>(
"getAltitude");
250 if (!qFuzzyIsNull(value))
251 coordinate.setAltitude(value);
258 if (useAltConverter && QNativeInterface::QAndroidApplication::sdkVersion() >= 34) {
259 attributeExists = jniObject.callMethod<jboolean>(
"hasMslAltitude");
260 if (attributeExists) {
261 const jdouble value = jniObject.callMethod<jdouble>(
"getMslAltitudeMeters");
262 if (!qFuzzyIsNull(value))
263 coordinate.setAltitude(value);
267 info.setCoordinate(coordinate);
270 const jlong timestamp = jniObject.callMethod<jlong>(
"getTime");
271 info.setTimestamp(QDateTime::fromMSecsSinceEpoch(timestamp, QTimeZone::UTC));
274 attributeExists = jniObject.callMethod<jboolean>(
"hasAccuracy");
275 if (attributeExists) {
276 const jfloat accuracy = jniObject.callMethod<jfloat>(
"getAccuracy");
277 if (!qFuzzyIsNull(accuracy))
278 info.setAttribute(QGeoPositionInfo::HorizontalAccuracy, qreal(accuracy));
282 attributeExists = jniObject.callMethod<jboolean>(
"hasVerticalAccuracy");
283 if (attributeExists) {
284 const jfloat accuracy = jniObject.callMethod<jfloat>(
"getVerticalAccuracyMeters");
285 if (!qFuzzyIsNull(accuracy))
286 info.setAttribute(QGeoPositionInfo::VerticalAccuracy, qreal(accuracy));
290 attributeExists = jniObject.callMethod<jboolean>(
"hasSpeed");
291 if (attributeExists) {
292 const jfloat speed = jniObject.callMethod<jfloat>(
"getSpeed");
293 if (!qFuzzyIsNull(speed))
294 info.setAttribute(QGeoPositionInfo::GroundSpeed, qreal(speed));
298 attributeExists = jniObject.callMethod<jboolean>(
"hasBearing");
299 if (attributeExists) {
300 const jfloat bearing = jniObject.callMethod<jfloat>(
"getBearing");
301 if (!qFuzzyIsNull(bearing))
302 info.setAttribute(QGeoPositionInfo::Direction, qreal(bearing));
305 const jfloat bearingAccuracy =
306 jniObject.callMethod<jfloat>(
"getBearingAccuracyDegrees");
307 if (!qFuzzyIsNull(bearingAccuracy))
308 info.setAttribute(QGeoPositionInfo::DirectionAccuracy, qreal(bearingAccuracy));
317 return std::make_pair(
static_cast<
int>(info.satelliteSystem()),
318 info.satelliteIdentifier());
322 QList<QGeoSatelliteInfo>* usedInFix)
324 QJniObject jniStatus(gnssStatus);
325 QList<QGeoSatelliteInfo> sats;
328 const int satellitesCount = jniStatus.callMethod<jint>(
"getSatelliteCount");
329 for (
int i = 0; i < satellitesCount; ++i) {
330 QGeoSatelliteInfo info;
335 const jfloat cn0 = jniStatus.callMethod<jfloat>(
"getCn0DbHz", i);
336 info.setSignalStrength(
static_cast<
int>(cn0));
339 const jint constellationType =
340 jniStatus.callMethod<jint>(
"getConstellationType", i);
341 info.setSatelliteSystem(ConstellationMapper::toSatelliteSystem(constellationType));
344 const jint svId = jniStatus.callMethod<jint>(
"getSvid", i);
345 info.setSatelliteIdentifier(svId);
348 const jfloat azimuth = jniStatus.callMethod<jfloat>(
"getAzimuthDegrees", i);
349 info.setAttribute(QGeoSatelliteInfo::Azimuth,
static_cast<qreal>(azimuth));
352 const jfloat elevation = jniStatus.callMethod<jfloat>(
"getElevationDegrees", i);
353 info.setAttribute(QGeoSatelliteInfo::Elevation,
static_cast<qreal>(elevation));
357 const jboolean inFix = jniStatus.callMethod<jboolean>(
"usedInFix", i);
359 const UniqueId id = getUid(info);
360 if (uids.contains(id))
367 usedInFix->append(info);
374 bool useAltitudeConverter)
378 return QGeoPositionInfo();
380 const auto accuracy = fromSatellitePositioningMethodsOnly
384 if (!hasPositioningPermissions(accuracy))
387 QJniObject locationObj = QJniObject::callStaticMethod<jobject>(
388 positioningClass(), lastKnownPositionMethodId, fromSatellitePositioningMethodsOnly,
389 useAltitudeConverter);
390 jobject location = locationObj.object();
391 if (location ==
nullptr)
392 return QGeoPositionInfo();
394 const QGeoPositionInfo info = positionInfoFromJavaLocation(location, useAltitudeConverter);
401 int providerSelection = 0;
402 if (m & QGeoPositionInfoSource::SatellitePositioningMethods)
403 providerSelection |= 1;
404 if (m & QGeoPositionInfoSource::NonSatellitePositioningMethods)
405 providerSelection |= 2;
407 return providerSelection;
414 if (m & QGeoPositionInfoSource::NonSatellitePositioningMethods)
416 if (m & QGeoPositionInfoSource::SatellitePositioningMethods)
425 return QGeoPositionInfoSource::UnknownSourceError;
430 const auto preferredMethods = source->preferredPositioningMethods();
431 const auto accuracy = accuracyFromPositioningMethods(preferredMethods);
432 if (!hasPositioningPermissions(accuracy))
433 return QGeoPositionInfoSource::AccessError;
435 int errorCode = QJniObject::callStaticMethod<jint>(
436 positioningClass(), startUpdatesMethodId, androidClassKey,
437 positioningMethodToInt(preferredMethods),
438 source->updateInterval(), source->useAltitudeConverter());
444 return static_cast<QGeoPositionInfoSource::Error>(errorCode);
450 return QGeoPositionInfoSource::UnknownSourceError;
456 QJniObject::callStaticMethod<
void>(positioningClass(), stopUpdatesMethodId,
464 return QGeoPositionInfoSource::UnknownSourceError;
469 const auto preferredMethods = source->preferredPositioningMethods();
470 const auto accuracy = accuracyFromPositioningMethods(preferredMethods);
471 if (!hasPositioningPermissions(accuracy))
472 return QGeoPositionInfoSource::AccessError;
474 int errorCode = QJniObject::callStaticMethod<jint>(
475 positioningClass(), requestUpdateMethodId, androidClassKey,
476 positioningMethodToInt(preferredMethods),
477 timeout, source->useAltitudeConverter());
483 return static_cast<QGeoPositionInfoSource::Error>(errorCode);
488 return QGeoPositionInfoSource::UnknownSourceError;
495 return QGeoSatelliteInfoSource::UnknownSourceError;
502 if (!hasPositioningPermissions(AccuracyType::Precise))
503 return QGeoSatelliteInfoSource::AccessError;
505 int interval = source->updateInterval();
507 interval = requestTimeout;
508 int errorCode = QJniObject::callStaticMethod<jint>(positioningClass(),
509 startSatelliteUpdatesMethodId,
510 androidClassKey, interval,
517 return static_cast<QGeoSatelliteInfoSource::Error>(errorCode);
519 qCWarning(lcPositioning)
520 <<
"startSatelliteUpdates: Unknown error code" << errorCode;
524 return QGeoSatelliteInfoSource::UnknownSourceError;
530 QLocationPermission permission;
533 if (!QNativeInterface::QAndroidApplication::isActivityContext())
534 permission.setAvailability(QLocationPermission::Always);
536 bool permitted =
false;
538 permission.setAccuracy(QLocationPermission::Precise);
539 permitted = qApp->checkPermission(permission) == Qt::PermissionStatus::Granted;
542 permission.setAccuracy(QLocationPermission::Approximate);
543 permitted |= qApp->checkPermission(permission) == Qt::PermissionStatus::Granted;
547 qCWarning(lcPositioning) <<
"Position data not available due to missing permission";
554 jint androidClassKey, jboolean isSingleUpdate)
561 qCWarning(lcPositioning) <<
"positionUpdated: source == 0";
566 QGeoPositionInfo info =
567 AndroidPositioning::positionInfoFromJavaLocation(location.object(), useAltitudeConverter);
571 QMetaObject::invokeMethod(source,
"processPositionUpdate", Qt::AutoConnection,
572 Q_ARG(QGeoPositionInfo, info));
574 QMetaObject::invokeMethod(source,
"processSinglePositionUpdate", Qt::AutoConnection,
575 Q_ARG(QGeoPositionInfo, info));
577Q_DECLARE_JNI_NATIVE_METHOD(positionUpdated)
583 QObject *source = AndroidPositioning::idToPosSource()->value(androidClassKey);
585 source = AndroidPositioning::idToSatSource()->value(androidClassKey);
587 qCWarning(lcPositioning) <<
"locationProvidersDisabled: source == 0";
591 QMetaObject::invokeMethod(source,
"locationProviderDisabled", Qt::AutoConnection);
593Q_DECLARE_JNI_NATIVE_METHOD(locationProvidersDisabled)
599 QObject *source = AndroidPositioning::idToPosSource()->value(androidClassKey);
601 qCWarning(lcPositioning) <<
"locationProvidersChanged: source == 0";
605 QMetaObject::invokeMethod(source,
"locationProvidersChanged", Qt::AutoConnection);
607Q_DECLARE_JNI_NATIVE_METHOD(locationProvidersChanged)
610 const QList<QGeoSatelliteInfo> &inUse,
611 jint androidClassKey, jboolean isSingleUpdate)
615 qCWarning(lcPositioning) <<
"notifySatelliteInfoUpdated: source == 0";
619 QMetaObject::invokeMethod(source,
"processSatelliteUpdate", Qt::AutoConnection,
620 Q_ARG(QList<QGeoSatelliteInfo>, inView),
621 Q_ARG(QList<QGeoSatelliteInfo>, inUse),
622 Q_ARG(
bool, isSingleUpdate));
626 jint androidClassKey, jboolean isSingleUpdate)
631 QList<QGeoSatelliteInfo> inUse;
632 QList<QGeoSatelliteInfo> sats =
633 AndroidPositioning::satelliteInfoFromJavaGnssStatus(gnssStatus.object(), &inUse);
635 notifySatelliteInfoUpdated(sats, inUse, androidClassKey, isSingleUpdate);
637Q_DECLARE_JNI_NATIVE_METHOD(satelliteGnssUpdated)
639#define GET_AND_CHECK_STATIC_METHOD(VAR, METHOD_NAME, ...)
640 VAR = env.findStaticMethod<__VA_ARGS__>(positioningClass(), METHOD_NAME);
642 __android_log_print(ANDROID_LOG_FATAL, logTag, methodErrorMsg, METHOD_NAME,
643 QtJniTypes::methodSignature<__VA_ARGS__>().data());
651 __android_log_print(ANDROID_LOG_FATAL,
logTag,
"Failed to create environment");
655 if (!positioningClass.init()) {
656 __android_log_print(ANDROID_LOG_FATAL,
logTag,
"Failed to create global class ref");
660 if (!env.registerNativeMethods(positioningClass(), {
661 Q_JNI_NATIVE_METHOD(positionUpdated),
662 Q_JNI_NATIVE_METHOD(locationProvidersDisabled),
663 Q_JNI_NATIVE_METHOD(locationProvidersChanged),
664 Q_JNI_NATIVE_METHOD(satelliteGnssUpdated)
666 __android_log_print(ANDROID_LOG_FATAL,
logTag,
"Failed to register native methods");
672 QtJniTypes::Location,
bool,
bool);
679 jint, jint, jint,
bool);
684extern "C" Q_DECL_EXPORT jint JNICALL JNI_OnLoad(JavaVM * ,
void * )
686 static bool initialized =
false;
688 return JNI_VERSION_1_6;
691 __android_log_print(ANDROID_LOG_INFO, logTag,
"Positioning start");
693 const auto context = QNativeInterface::QAndroidApplication::context();
694 QtJniTypes::QtPositioning::callStaticMethod<
void>(
"setContext", context);
696 if (!registerNatives()) {
697 __android_log_print(ANDROID_LOG_FATAL, logTag,
"registerNatives() failed");
701 if (!ConstellationMapper::init()) {
702 __android_log_print(ANDROID_LOG_ERROR, logTag,
703 "Failed to extract constellation type constants. "
704 "Satellite system will be undefined!");
707 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")