Qt 6.x
The Qt SDK
Loading...
Searching...
No Matches
qdarwinpermissionplugin_location.mm
Go to the documentation of this file.
1// Copyright (C) 2022 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
5
6#include <deque>
7
8#include <CoreLocation/CoreLocation.h>
9
10@interface QDarwinLocationPermissionHandler () <CLLocationManagerDelegate>
11@property (nonatomic, retain) CLLocationManager *manager;
12@end
13
14Q_LOGGING_CATEGORY(lcLocationPermission, "qt.permissions.location");
15
17{
18 // After creating a CLLocationManager the authorizationStatus
19 // will initially be kCLAuthorizationStatusNotDetermined. The
20 // status will then update to an actual status if the app was
21 // previously authorized/denied once the location services
22 // do some initial book-keeping in the background. By kicking
23 // off a CLLocationManager early on here, we ensure that by
24 // the time the user calls checkPermission the authorization
25 // status has been resolved.
26 qCDebug(lcLocationPermission) << "Warming up location services";
27 [[CLLocationManager new] release];
28}
29
31
33{
36};
37
39 std::deque<PermissionRequest> m_requests;
40}
41
42- (instancetype)init
43{
44 if ((self = [super init])) {
45 // The delegate callbacks will come in on the thread that
46 // the CLLocationManager is created on, and we want those
47 // to come in on the main thread, so we defer creation
48 // of the manger until requestPermission, where we know
49 // we are on the main thread.
50 self.manager = nil;
51 }
52
53 return self;
54}
55
56- (Qt::PermissionStatus)checkPermission:(QPermission)permission
57{
58 const auto locationPermission = *permission.value<QLocationPermission>();
59
60 auto status = [self authorizationStatus:locationPermission];
62 return status;
63
64 return [self accuracyAuthorization:locationPermission];
65}
66
67- (Qt::PermissionStatus)authorizationStatus:(QLocationPermission)permission
68{
69 NSString *bundleIdentifier = NSBundle.mainBundle.bundleIdentifier;
70 if (!bundleIdentifier || !bundleIdentifier.length) {
71 qCWarning(lcLocationPermission) << "Missing bundle identifier"
72 << "in Info.plist. Can not use location permissions.";
74 }
75
76 auto status = [self authorizationStatus];
77 switch (status) {
78 case kCLAuthorizationStatusRestricted:
79 case kCLAuthorizationStatusDenied:
81 case kCLAuthorizationStatusNotDetermined:
83 case kCLAuthorizationStatusAuthorizedAlways:
85#ifdef Q_OS_IOS
86 case kCLAuthorizationStatusAuthorizedWhenInUse:
87 if (permission.availability() == QLocationPermission::Always)
90#endif
91 }
92
93 qCWarning(lcPermissions) << "Unknown permission status" << status << "detected in" << self;
95}
96
97- (CLAuthorizationStatus)authorizationStatus
98{
99 if (self.manager) {
100 if (@available(macOS 11, iOS 14, *))
101 return self.manager.authorizationStatus;
102 }
103
104 return QT_IGNORE_DEPRECATIONS(CLLocationManager.authorizationStatus);
105}
106
107- (Qt::PermissionStatus)accuracyAuthorization:(QLocationPermission)permission
108{
109 auto status = CLAccuracyAuthorizationReducedAccuracy;
110 if (@available(macOS 11, iOS 14, *))
111 status = self.manager.accuracyAuthorization;
112
113 switch (status) {
114 case CLAccuracyAuthorizationFullAccuracy:
116 case CLAccuracyAuthorizationReducedAccuracy:
117 if (permission.accuracy() == QLocationPermission::Approximate)
119 else
121 }
122
123 qCWarning(lcPermissions) << "Unknown accuracy status" << status << "detected in" << self;
125}
126
127- (QStringList)usageDescriptionsFor:(QPermission)permission
128{
129#if defined(Q_OS_MACOS)
130 return { "NSLocationUsageDescription" };
131#else // iOS 11 and above
132 QStringList usageDescriptions = { "NSLocationWhenInUseUsageDescription" };
133 const auto locationPermission = *permission.value<QLocationPermission>();
134 if (locationPermission.availability() == QLocationPermission::Always)
135 usageDescriptions << "NSLocationAlwaysAndWhenInUseUsageDescription";
136 return usageDescriptions;
137#endif
138}
139
140- (void)requestPermission:(QPermission)permission withCallback:(PermissionCallback)callback
141{
142 const bool requestAlreadyInFlight = !m_requests.empty();
143
144 m_requests.push_back({ permission, callback });
145
146 if (requestAlreadyInFlight) {
147 qCDebug(lcLocationPermission).nospace() << "Already processing "
148 << m_requests.front().permission << ". Deferring request";
149 } else {
150 [self requestQueuedPermission];
151 }
152}
153
154- (void)requestQueuedPermission
155{
156 Q_ASSERT(!m_requests.empty());
157 const auto permission = m_requests.front().permission;
158
159 qCDebug(lcLocationPermission) << "Requesting" << permission;
160
161 if (!self.manager) {
162 self.manager = [[CLLocationManager new] autorelease];
163 self.manager.delegate = self;
164 }
165
166 const auto locationPermission = *permission.value<QLocationPermission>();
167 switch (locationPermission.availability()) {
169 // The documentation specifies that requestWhenInUseAuthorization can
170 // only be called when the current authorization status is undetermined.
171 switch ([self authorizationStatus]) {
172 case kCLAuthorizationStatusNotDetermined:
173 [self.manager requestWhenInUseAuthorization];
174 break;
175 default:
176 [self deliverResult];
177 }
178 break;
180 // The documentation specifies that requestAlwaysAuthorization can only
181 // be called when the current authorization status is either undetermined,
182 // or authorized when in use.
183 switch ([self authorizationStatus]) {
184 case kCLAuthorizationStatusNotDetermined:
185 [self.manager requestAlwaysAuthorization];
186 break;
187#ifdef Q_OS_IOS
188 case kCLAuthorizationStatusAuthorizedWhenInUse:
189 // Unfortunately when asking for AlwaysAuthorization when in
190 // the WhenInUse state, to "upgrade" the permission, the iOS
191 // location system will not give us a callback if the user
192 // denies the request, leaving us hanging without a way to
193 // respond to the permission request.
194 qCWarning(lcLocationPermission) << "QLocationPermission::WhenInUse"
195 << "can not be upgraded to QLocationPermission::Always on iOS."
196 << "Please request QLocationPermission::Always directly.";
198#endif
199 default:
200 [self deliverResult];
201 }
202 break;
203 }
204}
205
206- (void)locationManager:(CLLocationManager *)manager didChangeAuthorizationStatus:(CLAuthorizationStatus)status
207{
208 qCDebug(lcLocationPermission) << "Processing authorization"
209 << "update with status" << status;
210
211 if (m_requests.empty()) {
212 qCDebug(lcLocationPermission) << "No requests in flight. Ignoring.";
213 return;
214 }
215
216 if (status == kCLAuthorizationStatusNotDetermined) {
217 // Initializing a CLLocationManager will result in an initial
218 // callback to the delegate even before we've requested any
219 // location permissions. Normally we would ignore this callback
220 // due to the request queue check above, but if this callback
221 // comes in after the application has requested a permission
222 // we don't want to report the undetermined status, but rather
223 // wait for the actual result to come in.
224 qCDebug(lcLocationPermission) << "Ignoring delegate callback"
225 << "with status kCLAuthorizationStatusNotDetermined";
226 return;
227 }
228
229 [self deliverResult];
230}
231
232- (void)deliverResult
233{
234 auto request = m_requests.front();
235 m_requests.pop_front();
236
237 auto status = [self checkPermission:request.permission];
238 qCDebug(lcLocationPermission) << "Result for"
239 << request.permission << "was" << status;
240
241 request.callback(status);
242
243 if (!m_requests.empty()) {
244 qCDebug(lcLocationPermission) << "Still have"
245 << m_requests.size() << "deferred request(s)";
246 [self requestQueuedPermission];
247 }
248}
249
250@end
251
252#include "moc_qdarwinpermissionplugin_p_p.cpp"
Access the user's location.
Q_CORE_EXPORT Accuracy accuracy() const
Returns the accuracy of the request.
\inmodule QtCore \inheaderfile QPermissions
std::optional< T > value() const
\inmodule QtCore
void requestPermission(const QPermission &permission, const PermissionCallback &callback)
std::function< void(Qt::PermissionStatus)> PermissionCallback
QString self
Definition language.cpp:57
#define Q_FALLTHROUGH()
#define QT_IGNORE_DEPRECATIONS(statement)
Q_CONSTRUCTOR_FUNCTION(warmUpLocationServices)
void warmUpLocationServices()
DBusConnection const char DBusError DBusBusType DBusError return DBusConnection DBusHandleMessageFunction void DBusFreeFunction return DBusConnection return DBusConnection return const char DBusError return DBusConnection DBusMessage dbus_uint32_t return DBusConnection dbus_bool_t DBusConnection DBusAddWatchFunction DBusRemoveWatchFunction DBusWatchToggledFunction void DBusFreeFunction return DBusConnection DBusDispatchStatusFunction void DBusFreeFunction DBusTimeout return DBusTimeout return DBusWatch return DBusWatch unsigned int return DBusError const DBusError return const DBusMessage return DBusMessage return DBusMessage return DBusMessage return DBusMessage return DBusMessage return DBusMessageIter int const void return DBusMessageIter DBusMessageIter return DBusMessageIter void DBusMessageIter void int return DBusMessage DBusMessageIter return DBusMessageIter return DBusMessageIter DBusMessageIter const char const char const char const char return DBusMessage return DBusMessage const char return DBusMessage dbus_bool_t return DBusMessage dbus_uint32_t return DBusMessage void
#define Q_LOGGING_CATEGORY(name,...)
#define qCWarning(category,...)
#define qCDebug(category,...)
#define Q_ASSERT(cond)
Definition qrandom.cpp:47
static QT_BEGIN_NAMESPACE void init(QTextBoundaryFinder::BoundaryType type, QStringView str, QCharAttributes *attributes)
QNetworkAccessManager manager
QNetworkRequest request(url)