Source code

Revision control

Copy as Markdown

// Copyright (C) 2025 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#include "qohosgeopositioninfosource.h"
#include "qohospositioncommon.h"
#include "qohospositioningenums.h"
#include <QtCore/private/qcore_ohos_p.h>
#include <QtCore/private/qohoscommon_p.h>
#include <QtCore/private/qohosjstools_p.h>
#include <QtCore/private/qohoslogger_p.h>
#include <QtCore/qtimer.h>
#include <algorithm>
#include <cmath>
#include <cstdlib>
#include <functional>
#include <memory>
#include <optional>
#include <utility>
QT_BEGIN_NAMESPACE
namespace {
using UserActivityScenario = QtOhosPositioning::enums::ohos::geoLocationManager::UserActivityScenario;
using LocationError = QtOhosPositioning::enums::ohos::geoLocationManager::LocationError;
constexpr int coldStartMarginMs = 2 * 60 * 1000;
constexpr int continuousUpdateTimerIntervalMs = 30 * 1000;
const char *toStaticString(UserActivityScenario scenario)
{
switch (scenario) {
case UserActivityScenario::NAVIGATION:
return "NAVIGATION";
case UserActivityScenario::SPORT:
return "SPORT";
case UserActivityScenario::TRANSPORT:
return "TRANSPORT";
case UserActivityScenario::DAILY_LIFE_SERVICE:
return "DAILY_LIFE_SERVICE";
}
qOhosReportFatalErrorAndAbort(
"%s received invalid UserActivityScenario: %d", Q_FUNC_INFO, scenario);
}
const char *toStaticString(LocationError locationError)
{
switch (locationError) {
case LocationError::LOCATING_FAILED_DEFAULT:
return "LOCATING_FAILED_DEFAULT";
case LocationError::LOCATING_FAILED_LOCATION_PERMISSION_DENIED:
return "LOCATING_FAILED_LOCATION_PERMISSION_DENIED";
case LocationError::LOCATING_FAILED_BACKGROUND_PERMISSION_DENIED:
return "LOCATING_FAILED_BACKGROUND_PERMISSION_DENIED";
case LocationError::LOCATING_FAILED_LOCATION_SWITCH_OFF:
return "LOCATING_FAILED_LOCATION_SWITCH_OFF";
case LocationError::LOCATING_FAILED_INTERNET_ACCESS_FAILURE:
return "LOCATING_FAILED_INTERNET_ACCESS_FAILURE";
}
qOhosReportFatalErrorAndAbort(
"%s received invalid LocationError: %d", Q_FUNC_INFO, locationError);
}
std::optional<UserActivityScenario> tryMapPositioningMethodsToUserActivityScenario(
QGeoPositionInfoSource::PositioningMethods positioningMethods)
{
qOhosPrintfDebug("%s: %d", Q_FUNC_INFO, static_cast<int>(positioningMethods));
if (positioningMethods.testFlag(QGeoPositionInfoSource::SatellitePositioningMethods))
return UserActivityScenario::NAVIGATION;
if (positioningMethods.testFlag(QGeoPositionInfoSource::NonSatellitePositioningMethods))
return UserActivityScenario::DAILY_LIFE_SERVICE;
return std::nullopt;
}
QGeoPositionInfo convertLocationObjectToPositionInfo(const QNapi::Object &locationObject)
{
auto dateTime = QDateTime::fromMSecsSinceEpoch(
locationObject.get<QNapi::Number>("timeStamp").Int64Value());
QGeoCoordinate coordinate(
locationObject.get<QNapi::Number>("latitude"),
locationObject.get<QNapi::Number>("longitude"),
locationObject.get<QNapi::Number>("altitude"));
QGeoPositionInfo positionInfo(coordinate, dateTime);
positionInfo.setAttribute(
QGeoPositionInfo::Direction, locationObject.get<QNapi::Number>("direction"));
positionInfo.setAttribute(
QGeoPositionInfo::HorizontalAccuracy, locationObject.get<QNapi::Number>("accuracy"));
positionInfo.setAttribute(
QGeoPositionInfo::VerticalAccuracy, locationObject.get<QNapi::Number>("altitudeAccuracy"));
// NOTE: QGeoPositionInfo distinguishes GroundSpeed and VerticalSpeed, while OHOS provides only
// Location_BasicInfo::speed. Here, we assume, that since Location_BasicInfo::accuracy relates
// to HorizontalAccuracy, the Location_BasicInfo::speed relates to GroundSpeed (although, there
// is no information about that anywhere in the documentation).
positionInfo.setAttribute(
QGeoPositionInfo::GroundSpeed, locationObject.get<QNapi::Number>("speed"));
return positionInfo;
}
std::shared_ptr<void> registerLocationChangeUpdateHandler(
QObject *contextObject, long intervalSec,
UserActivityScenario userActivityScenario,
QOhosConsumer<const QGeoPositionInfo &> positionInfoUpdateConsumer)
{
auto sharedPositionInfoUpdateConsumer = QtOhos::moveToSharedPtr(std::move(positionInfoUpdateConsumer));
auto weakPositionInfoUpdateConsumer = QtOhos::makeWeakPtr(sharedPositionInfoUpdateConsumer);
auto contextObjectRef = QtOhos::makeQThreadSafeRef(contextObject);
auto registrationHandle = QOhosJsThreadGateway::eval(
[&](QOhosJsState &jsState) {
qOhosPrintfWarning("%s Interval in Seconds: %li", Q_FUNC_INFO, intervalSec);
auto continuousLocationRequest = QNapi::makeObject(
jsState.env(),
{
{"interval", intervalSec},
{"locationScenario", jsState.mapOhosEnumToJs(userActivityScenario)},
});
qOhosPrintfDebug(
"%s registering callback with scenario: %s",
Q_FUNC_INFO, toStaticString(userActivityScenario));
return QtOhos::makeProxyWithJsThreadDeleter(
registerQOhosOnOffMethodsBasedEventHandler(
getGeoLocationManagerObject(jsState), "locationChange",
[contextObjectRef, weakPositionInfoUpdateConsumer](const QOhosCallbackInfo &cbInfo) {
auto location = cbInfo.getFirstArg<QNapi::Object>(Q_FUNC_INFO);
auto positionInfo = convertLocationObjectToPositionInfo(location);
contextObjectRef.visitInQtThreadIfAlive(
[weakPositionInfoUpdateConsumer, positionInfo](auto &) {
auto sharedPositionInfoUpdateConsumer = weakPositionInfoUpdateConsumer.lock();
if (sharedPositionInfoUpdateConsumer)
(*sharedPositionInfoUpdateConsumer)(positionInfo);
});
},
{
.optEventSourceAliveCheckFunc = {},
.extraOnArg = std::make_optional<QNapi::ValueWrapper>(continuousLocationRequest),
.extraOffArg = {},
.optOnCallExceptionHandler = {},
}));
});
return QtOhos::moveToSharedPtr(
std::make_pair(sharedPositionInfoUpdateConsumer, registrationHandle));
}
std::shared_ptr<void> makeContinuousPositionInfoUpdateProducer(
QObject *contextObject, int intervalMs, QGeoPositionInfoSource::PositioningMethods positioningMethods,
QOhosConsumer<const QGeoPositionInfo &, QGeoPositionInfoSource::PositioningMethods> positionInfoUpdateConsumer)
{
auto userActivityScenario =
tryMapPositioningMethodsToUserActivityScenario(positioningMethods)
.value_or(UserActivityScenario::DAILY_LIFE_SERVICE);
auto intervalSec = std::lround(static_cast<double>(intervalMs) / 1000.0);
return registerLocationChangeUpdateHandler(
contextObject, intervalSec, userActivityScenario,
[positionInfoUpdateConsumer = std::move(positionInfoUpdateConsumer), positioningMethods](const QGeoPositionInfo &positionInfo) {
positionInfoUpdateConsumer(positionInfo, positioningMethods);
});
}
std::shared_ptr<void> makeSinglePositionInfoUpdateProducer(
QObject *contextObject, int timeoutMs, QGeoPositionInfoSource::PositioningMethods positioningMethods,
QOhosConsumer<std::optional<QGeoPositionInfo>, QGeoPositionInfoSource::PositioningMethods> positionInfoUpdateConsumer)
{
qOhosDebug(QtForOhos) << Q_FUNC_INFO << contextObject << timeoutMs;
struct Context
{
QOhosConsumer<std::optional<QGeoPositionInfo>, QGeoPositionInfoSource::PositioningMethods> positionInfoUpdateConsumer;
QtOhos::QThreadSafeRef<QObject> contextObjectRef;
};
auto sharedContext = QtOhos::moveToSharedPtr(Context{
.positionInfoUpdateConsumer = std::move(positionInfoUpdateConsumer),
.contextObjectRef = QtOhos::makeQThreadSafeRef(contextObject)
});
auto weakContext = QtOhos::makeWeakPtr(sharedContext);
auto sharedPositionInfoUpdateProxy = QtOhos::moveToSharedPtr(
[weakContext, positioningMethods](std::optional<QGeoPositionInfo> optPositionInfo) {
auto sharedContext = weakContext.lock();
if (sharedContext) {
sharedContext->contextObjectRef.visitInQtThreadIfAlive(
[weakContext, optPositionInfo, positioningMethods](auto &) {
auto sharedContext = weakContext.lock();
if (sharedContext)
sharedContext->positionInfoUpdateConsumer(optPositionInfo, positioningMethods);
});
}
});
QOhosJsThreadGateway::runAndWait(
[&](QOhosJsState &jsState) {
auto userActivityScenario =
tryMapPositioningMethodsToUserActivityScenario(positioningMethods)
.value_or(UserActivityScenario::DAILY_LIFE_SERVICE);
auto currentLocationRequest = QNapi::makeObject(
jsState.env(),
{
{"scenario", jsState.mapOhosEnumToJs(userActivityScenario)},
{"timeoutMs", timeoutMs},
});
getGeoLocationManagerObject(jsState)
.evalToPromiseOrRejectOnThrow("getCurrentLocation(*)", {currentLocationRequest})
.onThen([sharedPositionInfoUpdateProxy](const QOhosCallbackInfo &cbInfo) {
auto location = cbInfo.getFirstArg<QNapi::Object>("getCurrentLocation");
(*sharedPositionInfoUpdateProxy)(convertLocationObjectToPositionInfo(location));
})
.onCatch([sharedPositionInfoUpdateProxy](const QOhosCallbackInfo &cbInfo) {
QtOhos::logJsCallbackError(cbInfo, "getCurrentLocation() failed");
(*sharedPositionInfoUpdateProxy)(std::nullopt);
});
});
return sharedContext;
}
std::shared_ptr<void> registerLocationErrorHandler(
QObject *contextObject, QOhosConsumer<LocationError> errorConsumer)
{
qOhosDebug(QtForOhos) << Q_FUNC_INFO << contextObject;
auto sharedErrorConsumer = QtOhos::moveToSharedPtr(std::move(errorConsumer));
auto contextObjectRef = QtOhos::makeQThreadSafeRef(contextObject);
auto weakErrorConsumer = QtOhos::makeWeakPtr(sharedErrorConsumer);
auto registrationHandle = QOhosJsThreadGateway::eval(
[&](QOhosJsState &jsState) {
return QtOhos::makeProxyWithJsThreadDeleter(
registerQOhosOnOffMethodsBasedEventHandler(
getGeoLocationManagerObject(jsState), "locationError",
[contextObjectRef, weakErrorConsumer](const QOhosCallbackInfo &cbInfo) {
auto jsLocationError = cbInfo.getFirstArg<QNapi::Number>(Q_FUNC_INFO);
auto locationError = cbInfo.jsState().mapOhosEnumFromJs<LocationError>(jsLocationError);
contextObjectRef.visitInQtThreadIfAlive(
[weakErrorConsumer, locationError](auto &) {
auto sharedErrorConsumer = weakErrorConsumer.lock();
if (sharedErrorConsumer)
(*sharedErrorConsumer)(locationError);
});
}));
});
return QtOhos::makeSharedPtrWithAttachedExtraData(registrationHandle, sharedErrorConsumer);
}
QGeoPositionInfoSource::Error convertLocationErrorToQtError(LocationError error)
{
switch (error) {
case LocationError::LOCATING_FAILED_DEFAULT:
return QGeoPositionInfoSource::UnknownSourceError;
case LocationError::LOCATING_FAILED_LOCATION_SWITCH_OFF:
return QGeoPositionInfoSource::ClosedError;
case LocationError::LOCATING_FAILED_LOCATION_PERMISSION_DENIED:
case LocationError::LOCATING_FAILED_BACKGROUND_PERMISSION_DENIED:
case LocationError::LOCATING_FAILED_INTERNET_ACCESS_FAILURE:
return QGeoPositionInfoSource::AccessError;
}
qOhosPrintfWarning("%s received unknown error: %d", Q_FUNC_INFO, static_cast<int>(error));
return QGeoPositionInfoSource::UnknownSourceError;
}
class QOhosGeoPositionInfoSource : public QGeoPositionInfoSource
{
public:
static QOhosGeoPositionInfoSource *tryMakeInstance(QObject *parent);
~QOhosGeoPositionInfoSource() override;
void setUpdateInterval(int msec) override;
QGeoPositionInfo lastKnownPosition(bool fromSatellitePositioningMethodsOnly) const override;
PositioningMethods supportedPositioningMethods() const override;
void setPreferredPositioningMethods(PositioningMethods methods) override;
int minimumUpdateInterval() const override;
Error error() const override;
public:
void startUpdates() override;
void stopUpdates() override;
void requestUpdate(int timeout) override;
protected:
QOhosGeoPositionInfoSource(QObject *parent);
private:
void setErrorHelper(QGeoPositionInfoSource::Error error);
void continuousUpdateTimeout();
void updatePositionInfo(const QGeoPositionInfo &positionInfo, PositioningMethods positioningMethods);
QGeoPositionInfoSource::Error m_error = QGeoPositionInfoSource::NoError;
QGeoPositionInfo m_lastUpdatedPositionInfo;
QGeoPositionInfo m_lastUpdatedPositionInfoFromSatelliteSource;
std::shared_ptr<void> m_locationErrorHandle;
std::shared_ptr<void> m_continuousLocationUpdatesHandle;
QTimer m_continuousUpdateTimeoutTimer;
qint64 m_lastUpdateTime = 0;
bool m_continuousUpdateTimeoutErrorRaised = false;
std::shared_ptr<void> m_singlePositionUpdateHandle;
std::unique_ptr<QTimer> m_singleUpdateTimeoutTimer;
};
QOhosGeoPositionInfoSource *QOhosGeoPositionInfoSource::tryMakeInstance(QObject *parent)
{
if (!checkLocationOrApproximatelyLocationPermissionGranted()) {
qOhosDebug(QtForOhos)
<< Q_FUNC_INFO
<< "Location permission isn't granted. Can't create QOhosGeoPositionInfoSource";
return nullptr;
}
return new QOhosGeoPositionInfoSource(parent);
}
QOhosGeoPositionInfoSource::QOhosGeoPositionInfoSource(QObject *parent)
: QGeoPositionInfoSource(parent)
{
qOhosDebug(QtForOhos) << Q_FUNC_INFO << parent;
m_locationErrorHandle = registerLocationErrorHandler(
this,
[this](LocationError error) {
qOhosWarning(QtForOhos)
<< Q_FUNC_INFO << "Received error from callback:" << toStaticString(error);
setErrorHelper(convertLocationErrorToQtError(error));
});
m_continuousUpdateTimeoutTimer.setSingleShot(false);
m_continuousUpdateTimeoutTimer.setInterval(continuousUpdateTimerIntervalMs);
QObject::connect(&m_continuousUpdateTimeoutTimer, &QTimer::timeout,
this, [this]() { continuousUpdateTimeout(); });
}
QOhosGeoPositionInfoSource::~QOhosGeoPositionInfoSource() = default;
void QOhosGeoPositionInfoSource::setUpdateInterval(int msec)
{
qOhosDebug(QtForOhos) << Q_FUNC_INFO << msec;
QGeoPositionInfoSource::setUpdateInterval(msec);
if (m_continuousLocationUpdatesHandle) {
stopUpdates();
startUpdates();
}
}
QGeoPositionInfo QOhosGeoPositionInfoSource::lastKnownPosition(bool fromSatellitePositioningMethodsOnly) const
{
qOhosDebug(QtForOhos) << Q_FUNC_INFO << fromSatellitePositioningMethodsOnly;
return fromSatellitePositioningMethodsOnly
? m_lastUpdatedPositionInfoFromSatelliteSource
: m_lastUpdatedPositionInfo;
}
QGeoPositionInfoSource::PositioningMethods QOhosGeoPositionInfoSource::supportedPositioningMethods() const
{
qOhosDebug(QtForOhos) << Q_FUNC_INFO;
return QGeoPositionInfoSource::AllPositioningMethods;
}
void QOhosGeoPositionInfoSource::setPreferredPositioningMethods(PositioningMethods methods)
{
qOhosDebug(QtForOhos) << Q_FUNC_INFO << methods;
QGeoPositionInfoSource::setPreferredPositioningMethods(methods);
}
int QOhosGeoPositionInfoSource::minimumUpdateInterval() const
{
qOhosDebug(QtForOhos) << Q_FUNC_INFO;
return 1000;
}
QGeoPositionInfoSource::Error QOhosGeoPositionInfoSource::error() const
{
qOhosDebug(QtForOhos) << Q_FUNC_INFO;
return m_error;
}
void QOhosGeoPositionInfoSource::startUpdates()
{
qOhosDebug(QtForOhos) << Q_FUNC_INFO;
if (m_continuousLocationUpdatesHandle) {
qOhosDebug(QtForOhos) << Q_FUNC_INFO << "Continuous updates already running. Ignoring";
return;
}
auto intervalMs = std::max(updateInterval(), minimumUpdateInterval());
m_lastUpdateTime = QDateTime::currentMSecsSinceEpoch();
m_continuousUpdateTimeoutErrorRaised = false;
m_continuousLocationUpdatesHandle =
makeContinuousPositionInfoUpdateProducer(
this, intervalMs,
preferredPositioningMethods(),
[this](const QGeoPositionInfo &positionInfo, PositioningMethods positioningMethods) {
updatePositionInfo(positionInfo, positioningMethods);
});
m_continuousUpdateTimeoutTimer.start();
setErrorHelper(QGeoPositionInfoSource::NoError);
}
void QOhosGeoPositionInfoSource::continuousUpdateTimeout()
{
if (!m_continuousUpdateTimeoutErrorRaised) {
const auto now = QDateTime::currentMSecsSinceEpoch();
if ((now - m_lastUpdateTime) > (updateInterval() + coldStartMarginMs)) {
m_continuousUpdateTimeoutErrorRaised = true;
setErrorHelper(QGeoPositionInfoSource::UpdateTimeoutError);
}
}
}
void QOhosGeoPositionInfoSource::stopUpdates()
{
qOhosDebug(QtForOhos) << Q_FUNC_INFO;
m_continuousLocationUpdatesHandle.reset();
m_continuousUpdateTimeoutTimer.stop();
}
void QOhosGeoPositionInfoSource::requestUpdate(int timeoutMs)
{
qOhosDebug(QtForOhos) << Q_FUNC_INFO << timeoutMs;
if (m_singlePositionUpdateHandle) {
qOhosDebug(QtForOhos) << Q_FUNC_INFO << "Update already in progress. Ignoring this one";
return;
}
if (timeoutMs != 0 && timeoutMs < minimumUpdateInterval()) {
setErrorHelper(QGeoPositionInfoSource::UpdateTimeoutError);
return;
}
auto timeout = timeoutMs != 0 ? timeoutMs : coldStartMarginMs;
m_singlePositionUpdateHandle = makeSinglePositionInfoUpdateProducer(
this,
updateInterval() >= minimumUpdateInterval() ? updateInterval() : minimumUpdateInterval(),
preferredPositioningMethods(),
[this](std::optional<QGeoPositionInfo> optPositionInfo, PositioningMethods positioningMethods) {
m_singlePositionUpdateHandle.reset();
m_singleUpdateTimeoutTimer.reset();
if (optPositionInfo.has_value())
updatePositionInfo(optPositionInfo.value(), positioningMethods);
else
setErrorHelper(QGeoPositionInfoSource::UpdateTimeoutError);
});
if (!m_singlePositionUpdateHandle) {
qOhosWarning(QtForOhos) << Q_FUNC_INFO << "Failed to make single position info update producer";
setErrorHelper(QGeoPositionInfoSource::UnknownSourceError);
return;
}
m_singleUpdateTimeoutTimer =
makeSingleShotUpdateTimeoutTimer(timeout, [this]() {
m_singlePositionUpdateHandle.reset();
setErrorHelper(QGeoPositionInfoSource::UpdateTimeoutError);
});
setErrorHelper(QGeoPositionInfoSource::NoError);
}
void QOhosGeoPositionInfoSource::setErrorHelper(QGeoPositionInfoSource::Error newError)
{
qOhosDebug(QtForOhos) << Q_FUNC_INFO << newError;
if (m_error == newError)
return;
m_error = newError;
if (newError != QGeoPositionInfoSource::NoError)
Q_EMIT QGeoPositionInfoSource::errorOccurred(m_error);
}
void QOhosGeoPositionInfoSource::updatePositionInfo(
const QGeoPositionInfo &positionInfo, PositioningMethods positioningMethods)
{
qOhosDebug(QtForOhos) << Q_FUNC_INFO << positionInfo;
m_lastUpdateTime = QDateTime::currentMSecsSinceEpoch();
m_continuousUpdateTimeoutErrorRaised = false;
setErrorHelper(QGeoPositionInfoSource::NoError);
if (positioningMethods.testFlag(PositioningMethod::SatellitePositioningMethods))
m_lastUpdatedPositionInfoFromSatelliteSource = positionInfo;
m_lastUpdatedPositionInfo = positionInfo;
Q_EMIT positionUpdated(m_lastUpdatedPositionInfo);
}
}
QGeoPositionInfoSource *tryMakeQOhosGeoPositionInfoSource(QObject *parent)
{
return QOhosGeoPositionInfoSource::tryMakeInstance(parent);
}
QT_END_NAMESPACE