Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
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_STATIC_LOGGING_CATEGORY(lcLocationPermission, "qt.permissions.location");
15
16namespace {
17
18void warmUpLocationServices()
19{
20 // After creating a CLLocationManager the authorizationStatus
21 // will initially be kCLAuthorizationStatusNotDetermined. The
22 // status will then update to an actual status if the app was
23 // previously authorized/denied once the location services
24 // do some initial book-keeping in the background. By kicking
25 // off a CLLocationManager early on here, we ensure that by
26 // the time the user calls checkPermission the authorization
27 // status has been resolved.
28 qCDebug(lcLocationPermission) << "Warming up location services";
29 [[CLLocationManager new] release];
30}
31
32Q_CONSTRUCTOR_FUNCTION(warmUpLocationServices);
33
34struct PermissionRequest
35{
36 QPermission permission;
37 PermissionCallback callback;
38};
39
40} // namespace
41
42@implementation QDarwinLocationPermissionHandler {
43 std::deque<PermissionRequest> m_requests;
44}
45
46- (instancetype)init
47{
48 if ((self = [super init])) {
49 // The delegate callbacks will come in on the thread that
50 // the CLLocationManager is created on, and we want those
51 // to come in on the main thread, so we defer creation
52 // of the manger until requestPermission, where we know
53 // we are on the main thread.
54 self.manager = nil;
55 }
56
57 return self;
58}
59
60- (Qt::PermissionStatus)checkPermission:(QPermission)permission
61{
62 const auto locationPermission = *permission.value<QLocationPermission>();
63
64 auto status = [self authorizationStatus:locationPermission];
65 if (status != Qt::PermissionStatus::Granted)
66 return status;
67
68 return [self accuracyAuthorization:locationPermission];
69}
70
71- (Qt::PermissionStatus)authorizationStatus:(QLocationPermission)permission
72{
73 NSString *bundleIdentifier = NSBundle.mainBundle.bundleIdentifier;
74 if (!bundleIdentifier || !bundleIdentifier.length) {
75 qCWarning(lcLocationPermission) << "Missing bundle identifier"
76 << "in Info.plist. Can not use location permissions.";
77 return Qt::PermissionStatus::Denied;
78 }
79
80#if defined(Q_OS_VISIONOS)
81 if (permission.availability() == QLocationPermission::Always)
82 return Qt::PermissionStatus::Denied;
83#endif
84
85 auto status = [self authorizationStatus];
86 switch (status) {
87 case kCLAuthorizationStatusRestricted:
88 case kCLAuthorizationStatusDenied:
89 return Qt::PermissionStatus::Denied;
90 case kCLAuthorizationStatusNotDetermined:
91 return Qt::PermissionStatus::Undetermined;
92#if !defined(Q_OS_VISIONOS)
93 case kCLAuthorizationStatusAuthorizedAlways:
94 return Qt::PermissionStatus::Granted;
95#endif
96#if defined(Q_OS_IOS) || defined(Q_OS_VISIONOS)
97 case kCLAuthorizationStatusAuthorizedWhenInUse:
98 if (permission.availability() == QLocationPermission::Always)
99 return Qt::PermissionStatus::Denied;
100 return Qt::PermissionStatus::Granted;
101#endif
102 }
103
104 qCWarning(lcPermissions) << "Unknown permission status" << status << "detected in" << self;
105 return Qt::PermissionStatus::Denied;
106}
107
108- (CLAuthorizationStatus)authorizationStatus
109{
110 if (self.manager)
111 return self.manager.authorizationStatus;
112
113 return QT_IGNORE_DEPRECATIONS(CLLocationManager.authorizationStatus);
114}
115
116- (Qt::PermissionStatus)accuracyAuthorization:(QLocationPermission)permission
117{
118 auto status = self.manager.accuracyAuthorization;
119
120 switch (status) {
121 case CLAccuracyAuthorizationFullAccuracy:
122 return Qt::PermissionStatus::Granted;
123 case CLAccuracyAuthorizationReducedAccuracy:
124 if (permission.accuracy() == QLocationPermission::Approximate)
125 return Qt::PermissionStatus::Granted;
126 else
127 return Qt::PermissionStatus::Denied;
128 }
129
130 qCWarning(lcPermissions) << "Unknown accuracy status" << status << "detected in" << self;
131 return Qt::PermissionStatus::Denied;
132}
133
134- (QStringList)usageDescriptionsFor:(QPermission)permission
135{
136#if defined(Q_OS_MACOS)
137 return { "NSLocationUsageDescription" };
138#else // iOS 11 and above
139 QStringList usageDescriptions = { "NSLocationWhenInUseUsageDescription" };
140 const auto locationPermission = *permission.value<QLocationPermission>();
141 if (locationPermission.availability() == QLocationPermission::Always)
142 usageDescriptions << "NSLocationAlwaysAndWhenInUseUsageDescription";
143 return usageDescriptions;
144#endif
145}
146
147- (void)requestPermission:(QPermission)permission withCallback:(PermissionCallback)callback
148{
149 const bool requestAlreadyInFlight = !m_requests.empty();
150
151 m_requests.push_back({ permission, callback });
152
153 if (requestAlreadyInFlight) {
154 qCDebug(lcLocationPermission).nospace() << "Already processing "
155 << m_requests.front().permission << ". Deferring request";
156 } else {
157 [self requestQueuedPermission];
158 }
159}
160
161- (void)requestQueuedPermission
162{
163 Q_ASSERT(!m_requests.empty());
164 const auto permission = m_requests.front().permission;
165
166 qCDebug(lcLocationPermission) << "Requesting" << permission;
167
168 if (!self.manager) {
169 self.manager = [[CLLocationManager new] autorelease];
170 self.manager.delegate = self;
171 }
172
173 const auto locationPermission = *permission.value<QLocationPermission>();
174 switch (locationPermission.availability()) {
175 case QLocationPermission::WhenInUse:
176 // The documentation specifies that requestWhenInUseAuthorization can
177 // only be called when the current authorization status is undetermined.
178 switch ([self authorizationStatus]) {
179 case kCLAuthorizationStatusNotDetermined:
180 [self.manager requestWhenInUseAuthorization];
181 break;
182 default:
183 [self deliverResult];
184 }
185 break;
186 case QLocationPermission::Always:
187#if defined(Q_OS_VISIONOS)
188 [self deliverResult]; // Not supported
189#else
190 // The documentation specifies that requestAlwaysAuthorization can only
191 // be called when the current authorization status is either undetermined,
192 // or authorized when in use.
193 switch ([self authorizationStatus]) {
194 case kCLAuthorizationStatusNotDetermined:
195 [self.manager requestAlwaysAuthorization];
196 break;
197#ifdef Q_OS_IOS
198 case kCLAuthorizationStatusAuthorizedWhenInUse:
199 // Unfortunately when asking for AlwaysAuthorization when in
200 // the WhenInUse state, to "upgrade" the permission, the iOS
201 // location system will not give us a callback if the user
202 // denies the request, leaving us hanging without a way to
203 // respond to the permission request.
204 qCWarning(lcLocationPermission) << "QLocationPermission::WhenInUse"
205 << "can not be upgraded to QLocationPermission::Always on iOS."
206 << "Please request QLocationPermission::Always directly.";
207 Q_FALLTHROUGH();
208#endif
209 default:
210 [self deliverResult];
211 }
212#endif
213 break;
214 }
215}
216
217- (void)locationManager:(CLLocationManager *)manager didChangeAuthorizationStatus:(CLAuthorizationStatus)status
218{
219 qCDebug(lcLocationPermission) << "Processing authorization"
220 << "update with status" << status;
221
222 if (m_requests.empty()) {
223 qCDebug(lcLocationPermission) << "No requests in flight. Ignoring.";
224 return;
225 }
226
227 if (status == kCLAuthorizationStatusNotDetermined) {
228 // Initializing a CLLocationManager will result in an initial
229 // callback to the delegate even before we've requested any
230 // location permissions. Normally we would ignore this callback
231 // due to the request queue check above, but if this callback
232 // comes in after the application has requested a permission
233 // we don't want to report the undetermined status, but rather
234 // wait for the actual result to come in.
235 qCDebug(lcLocationPermission) << "Ignoring delegate callback"
236 << "with status kCLAuthorizationStatusNotDetermined";
237 return;
238 }
239
240 [self deliverResult];
241}
242
243- (void)deliverResult
244{
245 auto request = m_requests.front();
246 m_requests.pop_front();
247
248 auto status = [self checkPermission:request.permission];
249 qCDebug(lcLocationPermission) << "Result for"
250 << request.permission << "was" << status;
251
252 request.callback(status);
253
254 if (!m_requests.empty()) {
255 qCDebug(lcLocationPermission) << "Still have"
256 << m_requests.size() << "deferred request(s)";
257 [self requestQueuedPermission];
258 }
259}
260
261@end
262
263#include "moc_qdarwinpermissionplugin_p_p.cpp"
QList< QString > QStringList
Constructs a string list that contains the given string, str.