Revision control

++ // Copyright (C) 2016 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
++ // Qt-Security score:significant reason:default
++
++ #include "qgeopositioninfosource_cl_p.h"
++ #include <QtCore/QTimerEvent>
++ #include <QtCore/QDebug>
++ #include <QtCore/qdatetime.h>
++ #include <QtCore/qglobal.h>
++ #include <QtCore/private/qglobal_p.h>
++ #include <QtCore/qtimezone.h>
++ #include <QtCore/QPermission>
++ #include <QtCore/QCoreApplication>
++ #include <QtCore/QVariantMap>
++
++ using namespace Qt::Literals::StringLiterals;
++
++ static constexpr auto kAllowAutoPause = "allowAutoPause"_L1;
++
++ #define MINIMUM_UPDATE_INTERVAL 1000
++
++ @interface PositionLocationDelegate : NSObject <CLLocationManagerDelegate>
++ @end
++
++ @implementation PositionLocationDelegate
++ {
++ QGeoPositionInfoSourceCL *m_positionInfoSource;
++ }
++
++ - (instancetype)initWithInfoSource:(QGeoPositionInfoSourceCL*) positionInfoSource
++ {
++ if ((self = [self init])) {
++ m_positionInfoSource = positionInfoSource;
++ }
++ return self;
++ }
++
++ - (void)locationManagerDidChangeAuthorization: (CLLocationManager *)manager
++ {
++ // Since Qt 6.6 the application requests the desired permissions.
++ // This delegate method is invoked always upon CLLocationManager
++ // instantiation, and later if authorization status changes.
++ m_positionInfoSource->changeAuthorizationStatus([manager authorizationStatus]);
++ }
++
++ - (void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation
++ {
++ Q_UNUSED(manager);
++ Q_UNUSED(oldLocation);
++
++ // Convert location timestamp to QDateTime
++ NSTimeInterval locationTimeStamp = [newLocation.timestamp timeIntervalSince1970];
++ const QDateTime timeStamp = QDateTime::fromMSecsSinceEpoch(qRound64(locationTimeStamp * 1000),
++ QTimeZone::UTC);
++
++ // Construct position info from location data
++ QGeoPositionInfo location(QGeoCoordinate(newLocation.coordinate.latitude,
++ newLocation.coordinate.longitude,
++ newLocation.altitude),
++ timeStamp);
++ if (newLocation.horizontalAccuracy >= 0)
++ location.setAttribute(QGeoPositionInfo::HorizontalAccuracy, newLocation.horizontalAccuracy);
++ if (newLocation.verticalAccuracy >= 0)
++ location.setAttribute(QGeoPositionInfo::VerticalAccuracy, newLocation.verticalAccuracy);
++ if (newLocation.course >= 0) {
++ location.setAttribute(QGeoPositionInfo::Direction, newLocation.course);
++ if (__builtin_available(iOS 13.4, macOS 10.15.4, *)) {
++ if (newLocation.courseAccuracy >= 0) {
++ location.setAttribute(QGeoPositionInfo::DirectionAccuracy,
++ newLocation.courseAccuracy);
++ }
++ }
++ }
++ if (newLocation.speed >= 0)
++ location.setAttribute(QGeoPositionInfo::GroundSpeed, newLocation.speed);
++
++ m_positionInfoSource->locationDataAvailable(location);
++ }
++
++ - (void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error
++ {
++ Q_UNUSED(manager);
++ m_positionInfoSource->setError(QGeoPositionInfoSource::AccessError);
++
++ qWarning() << QString::fromNSString([error localizedDescription]);
++
++ if ([error code] == 0
++ && QString::fromNSString([error domain]) == QStringLiteral("kCLErrorDomain"))
++ qWarning() << "(is Wi-Fi turned on?)";
++ }
++ @end
++
++ QT_BEGIN_NAMESPACE
++
++ QGeoPositionInfoSourceCL::QGeoPositionInfoSourceCL(const QVariantMap &parameters, QObject *parent)
++ : QGeoPositionInfoSource(parent),
++ m_locationManager(0),
++ m_updatesWanted(false),
++ m_updateTimer(0),
++ m_updateTimeout(0),
++ m_positionError(QGeoPositionInfoSource::NoError)
++ {
++ parseParameters(parameters);
++ }
++
++ QGeoPositionInfoSourceCL::~QGeoPositionInfoSourceCL()
++ {
++ stopUpdates();
++ [m_locationManager release];
++ }
++
++ void QGeoPositionInfoSourceCL::setUpdateInterval(int msec)
++ {
++ // If msec is 0 we send updates as data becomes available, otherwise we force msec to be equal
++ // to or larger than the minimum update interval.
++ if (msec != 0 && msec < minimumUpdateInterval())
++ msec = minimumUpdateInterval();
++
++ QGeoPositionInfoSource::setUpdateInterval(msec);
++
++ // Must timeout if update takes longer than specified interval
++ m_updateTimeout = msec;
++ if (m_updatesWanted)
++ setTimeoutInterval(m_updateTimeout);
++ }
++
++ bool QGeoPositionInfoSourceCL::enableLocationManager()
++ {
++ if (!m_locationManager) {
++ m_locationManager = [[CLLocationManager alloc] init];
++
++ #if defined(Q_OS_IOS)
++ NSDictionary<NSString *, id> *infoDict = [[NSBundle mainBundle] infoDictionary];
++ if (id value = [infoDict objectForKey:@"UIBackgroundModes"]) {
++ if ([value isKindOfClass:[NSArray class]]) {
++ NSArray *modes = static_cast<NSArray *>(value);
++ for (id mode in modes) {
++ if ([@"location" isEqualToString:mode]) {
++ m_locationManager.allowsBackgroundLocationUpdates = YES;
++ m_locationManager.pausesLocationUpdatesAutomatically =
++ m_allowAutoPause ? YES : NO;
++ break;
++ }
++ }
++ }
++ }
++ #endif
++
++ m_locationManager.desiredAccuracy = kCLLocationAccuracyBest;
++ m_locationManager.delegate = [[PositionLocationDelegate alloc] initWithInfoSource:this];
++ }
++
++ return qApp->checkPermission(QLocationPermission{}) == Qt::PermissionStatus::Granted;
++ }
++
++ void QGeoPositionInfoSourceCL::setTimeoutInterval(int msec)
++ {
++ // Start timeout timer
++ if (m_updateTimer) killTimer(m_updateTimer);
++ if (msec > 0) m_updateTimer = startTimer(msec);
++ else m_updateTimer = 0;
++ }
++
++ void QGeoPositionInfoSourceCL::parseParameters(const QVariantMap &parameters)
++ {
++ m_allowAutoPause = parameters.value(kAllowAutoPause, true).toBool();
++ }
++
++ void QGeoPositionInfoSourceCL::startUpdates()
++ {
++ m_positionError = QGeoPositionInfoSource::NoError;
++ m_updatesWanted = true;
++ if (enableLocationManager()) {
++ [m_locationManager startUpdatingLocation];
++ setTimeoutInterval(m_updateTimeout);
++ } else {
++ setError(QGeoPositionInfoSource::AccessError);
++ }
++ }
++
++ void QGeoPositionInfoSourceCL::stopUpdates()
++ {
++ if (m_locationManager) {
++ [m_locationManager stopUpdatingLocation];
++ m_updatesWanted = false;
++
++ // Stop timeout timer
++ setTimeoutInterval(0);
++ } else {
++ setError(QGeoPositionInfoSource::AccessError);
++ }
++ }
++
++ void QGeoPositionInfoSourceCL::requestUpdate(int timeout)
++ {
++ // Get a single update within timeframe
++ m_positionError = QGeoPositionInfoSource::NoError;
++ if (timeout < minimumUpdateInterval() && timeout != 0)
++ setError(QGeoPositionInfoSource::UpdateTimeoutError);
++ else if (enableLocationManager()) {
++ // This will force LM to generate a new update
++ [m_locationManager stopUpdatingLocation];
++ [m_locationManager startUpdatingLocation];
++ setTimeoutInterval(timeout);
++ } else {
++ setError(QGeoPositionInfoSource::AccessError);
++ }
++ }
++
++ void QGeoPositionInfoSourceCL::changeAuthorizationStatus(CLAuthorizationStatus status)
++ {
++ #ifndef Q_OS_MACOS
++ if (status == kCLAuthorizationStatusAuthorizedAlways
++ || status == kCLAuthorizationStatusAuthorizedWhenInUse
++ #else
++ if (status == kCLAuthorizationStatusAuthorized
++ #endif
++ ) {
++ if (m_updatesWanted)
++ startUpdates();
++ }
++ }
++
++ void QGeoPositionInfoSourceCL::timerEvent( QTimerEvent * event )
++ {
++ // Update timed out?
++ if (event->timerId() == m_updateTimer) {
++ setError(QGeoPositionInfoSource::UpdateTimeoutError);
++
++ // Only timeout once since last data
++ setTimeoutInterval(0);
++
++ // Started for single update?
++ if (!m_updatesWanted)
++ stopUpdates();
++ }
++ }
++
++ QGeoPositionInfoSource::PositioningMethods QGeoPositionInfoSourceCL::supportedPositioningMethods() const
++ {
++ // CoreLocation doesn't say which positioning method(s) it used
++ return QGeoPositionInfoSource::AllPositioningMethods;
++ }
++
++ int QGeoPositionInfoSourceCL::minimumUpdateInterval() const
++ {
++ return MINIMUM_UPDATE_INTERVAL;
++ }
++
++ void QGeoPositionInfoSourceCL::locationDataAvailable(QGeoPositionInfo location)
++ {
++ // Signal position data available
++ m_lastUpdate = location;
++ emit positionUpdated(location);
++
++ // Started for single update?
++ if (!m_updatesWanted)
++ stopUpdates();
++ // ...otherwise restart timeout timer
++ else setTimeoutInterval(m_updateTimeout);
++ }
++
++ QGeoPositionInfo QGeoPositionInfoSourceCL::lastKnownPosition(bool fromSatellitePositioningMethodsOnly) const
++ {
++ Q_UNUSED(fromSatellitePositioningMethodsOnly);
++
++ return m_lastUpdate;
++ }
++
++ QGeoPositionInfoSource::Error QGeoPositionInfoSourceCL::error() const
++ {
++ return m_positionError;
++ }
++
++ void QGeoPositionInfoSourceCL::setError(QGeoPositionInfoSource::Error positionError)
++ {
++ m_positionError = positionError;
++ if (m_positionError != QGeoPositionInfoSource::NoError)
++ emit QGeoPositionInfoSource::errorOccurred(positionError);
++ }
++
++ QT_END_NAMESPACE
++
++ #include "moc_qgeopositioninfosource_cl_p.cpp"