summaryrefslogtreecommitdiffstats
path: root/src/corelib/platform/darwin
diff options
context:
space:
mode:
Diffstat (limited to 'src/corelib/platform/darwin')
-rw-r--r--src/corelib/platform/darwin/qdarwinpermissionplugin.mm90
-rw-r--r--src/corelib/platform/darwin/qdarwinpermissionplugin_bluetooth.mm86
-rw-r--r--src/corelib/platform/darwin/qdarwinpermissionplugin_calendar.mm71
-rw-r--r--src/corelib/platform/darwin/qdarwinpermissionplugin_camera.mm42
-rw-r--r--src/corelib/platform/darwin/qdarwinpermissionplugin_contacts.mm58
-rw-r--r--src/corelib/platform/darwin/qdarwinpermissionplugin_location.mm263
-rw-r--r--src/corelib/platform/darwin/qdarwinpermissionplugin_microphone.mm42
-rw-r--r--src/corelib/platform/darwin/qdarwinpermissionplugin_p.h58
-rw-r--r--src/corelib/platform/darwin/qdarwinpermissionplugin_p_p.h104
9 files changed, 814 insertions, 0 deletions
diff --git a/src/corelib/platform/darwin/qdarwinpermissionplugin.mm b/src/corelib/platform/darwin/qdarwinpermissionplugin.mm
new file mode 100644
index 0000000000..5c527f396c
--- /dev/null
+++ b/src/corelib/platform/darwin/qdarwinpermissionplugin.mm
@@ -0,0 +1,90 @@
+// Copyright (C) 2022 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 "qdarwinpermissionplugin_p.h"
+
+QT_BEGIN_NAMESPACE
+
+QDarwinPermissionPlugin::QDarwinPermissionPlugin(QDarwinPermissionHandler *handler)
+ : QPermissionPlugin()
+ , m_handler(handler)
+{
+}
+
+QDarwinPermissionPlugin::~QDarwinPermissionPlugin()
+{
+ [m_handler release];
+}
+
+Qt::PermissionStatus QDarwinPermissionPlugin::checkPermission(const QPermission &permission)
+{
+ return [m_handler checkPermission:permission];
+}
+
+void QDarwinPermissionPlugin::requestPermission(const QPermission &permission, const PermissionCallback &callback)
+{
+ if (!verifyUsageDescriptions(permission)) {
+ callback(Qt::PermissionStatus::Denied);
+ return;
+ }
+
+ [m_handler requestPermission:permission withCallback:[=](Qt::PermissionStatus status) {
+ // In case the callback comes in on a secondary thread we need to marshal it
+ // back to the main thread. And if it doesn't, we still want to propagate it
+ // via an event, to avoid any GCD locks deadlocking the application on iOS
+ // if the user responds to the result by running a nested event loop.
+ // Luckily Qt::QueuedConnection gives us exactly what we need.
+ QMetaObject::invokeMethod(this, "permissionUpdated", Qt::QueuedConnection,
+ Q_ARG(Qt::PermissionStatus, status), Q_ARG(PermissionCallback, callback));
+ }];
+}
+
+void QDarwinPermissionPlugin::permissionUpdated(Qt::PermissionStatus status, const PermissionCallback &callback)
+{
+ callback(status);
+}
+
+bool QDarwinPermissionPlugin::verifyUsageDescriptions(const QPermission &permission)
+{
+ // FIXME: Look up the responsible process and inspect that,
+ // as that's what needs to have the usage descriptions.
+ // FIXME: Verify entitlements if the process is sandboxed.
+ auto *infoDictionary = NSBundle.mainBundle.infoDictionary;
+ for (auto description : [m_handler usageDescriptionsFor:permission]) {
+ if (!infoDictionary[description.toNSString()]) {
+ qCWarning(lcPermissions) <<
+ "Requesting" << permission.type().name() <<
+ "requires" << description << "in Info.plist";
+ return false;
+ }
+ }
+ return true;
+}
+
+QT_END_NAMESPACE
+
+QT_USE_NAMESPACE
+
+@implementation QDarwinPermissionHandler
+
+- (Qt::PermissionStatus)checkPermission:(QPermission)permission
+{
+ Q_UNREACHABLE(); // All handlers should at least provide a check
+}
+
+- (void)requestPermission:(QPermission)permission withCallback:(PermissionCallback)callback
+{
+ Q_UNUSED(permission);
+ qCWarning(lcPermissions).nospace() << "Could not request " << permission.type().name() << ". "
+ << "Please make sure you have included the required usage description in your Info.plist";
+ callback(Qt::PermissionStatus::Denied);
+}
+
+- (QStringList)usageDescriptionsFor:(QPermission)permission
+{
+ return {};
+}
+
+@end
+
+#include "moc_qdarwinpermissionplugin_p.cpp"
diff --git a/src/corelib/platform/darwin/qdarwinpermissionplugin_bluetooth.mm b/src/corelib/platform/darwin/qdarwinpermissionplugin_bluetooth.mm
new file mode 100644
index 0000000000..0cd375561f
--- /dev/null
+++ b/src/corelib/platform/darwin/qdarwinpermissionplugin_bluetooth.mm
@@ -0,0 +1,86 @@
+// Copyright (C) 2022 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 "qdarwinpermissionplugin_p_p.h"
+
+#include <deque>
+
+#include <CoreBluetooth/CoreBluetooth.h>
+
+@interface QDarwinBluetoothPermissionHandler () <CBCentralManagerDelegate>
+@property (nonatomic, retain) CBCentralManager *manager;
+@end
+
+@implementation QDarwinBluetoothPermissionHandler {
+ std::deque<PermissionCallback> m_callbacks;
+}
+
+- (instancetype)init
+{
+ if ((self = [super init]))
+ self.manager = nil;
+
+ return self;
+}
+
+- (Qt::PermissionStatus)checkPermission:(QPermission)permission
+{
+ Q_UNUSED(permission);
+ return [self currentStatus];
+}
+
+- (Qt::PermissionStatus)currentStatus
+{
+ auto status = CBCentralManager.authorization;
+ switch (status) {
+ case CBManagerAuthorizationNotDetermined:
+ return Qt::PermissionStatus::Undetermined;
+ case CBManagerAuthorizationRestricted:
+ case CBManagerAuthorizationDenied:
+ return Qt::PermissionStatus::Denied;
+ case CBManagerAuthorizationAllowedAlways:
+ return Qt::PermissionStatus::Granted;
+ }
+
+ qCWarning(lcPermissions) << "Unknown permission status" << status << "detected in" << self;
+ return Qt::PermissionStatus::Denied;
+}
+
+- (void)requestPermission:(QPermission)permission withCallback:(PermissionCallback)callback
+{
+ m_callbacks.push_back(callback);
+ if (!self.manager) {
+ self.manager = [[[CBCentralManager alloc]
+ initWithDelegate:self queue:dispatch_get_main_queue()] autorelease];
+ }
+}
+
+- (void)centralManagerDidUpdateState:(CBCentralManager *)manager
+{
+ Q_ASSERT(manager == self.manager);
+ Q_ASSERT(!m_callbacks.empty());
+
+ auto status = [self currentStatus];
+
+ for (auto callback : m_callbacks)
+ callback(status);
+
+ m_callbacks = {};
+ self.manager = nil;
+}
+
+- (QStringList)usageDescriptionsFor:(QPermission)permission
+{
+ Q_UNUSED(permission);
+#ifdef Q_OS_MACOS
+ if (QOperatingSystemVersion::current() > QOperatingSystemVersion::MacOSBigSur)
+#endif
+ {
+ return { "NSBluetoothAlwaysUsageDescription" };
+ }
+
+ return {};
+}
+@end
+
+#include "moc_qdarwinpermissionplugin_p_p.cpp"
diff --git a/src/corelib/platform/darwin/qdarwinpermissionplugin_calendar.mm b/src/corelib/platform/darwin/qdarwinpermissionplugin_calendar.mm
new file mode 100644
index 0000000000..a3eddd6d8f
--- /dev/null
+++ b/src/corelib/platform/darwin/qdarwinpermissionplugin_calendar.mm
@@ -0,0 +1,71 @@
+// Copyright (C) 2022 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 "qdarwinpermissionplugin_p_p.h"
+
+#include <EventKit/EventKit.h>
+
+@interface QDarwinCalendarPermissionHandler ()
+@property (nonatomic, retain) EKEventStore *eventStore;
+@end
+
+@implementation QDarwinCalendarPermissionHandler
+- (Qt::PermissionStatus)checkPermission:(QPermission)permission
+{
+ Q_UNUSED(permission);
+ return [self currentStatus];
+}
+
+- (Qt::PermissionStatus)currentStatus
+{
+ auto status = [EKEventStore authorizationStatusForEntityType:EKEntityTypeEvent];
+ switch (status) {
+ case EKAuthorizationStatusNotDetermined:
+ return Qt::PermissionStatus::Undetermined;
+ case EKAuthorizationStatusRestricted:
+ case EKAuthorizationStatusDenied:
+ return Qt::PermissionStatus::Denied;
+ case EKAuthorizationStatusAuthorized:
+ return Qt::PermissionStatus::Granted;
+#if QT_MACOS_IOS_PLATFORM_SDK_EQUAL_OR_ABOVE(140000, 170000)
+ case EKAuthorizationStatusWriteOnly:
+ // FIXME: Add WriteOnly AccessMode
+ return Qt::PermissionStatus::Denied;
+#endif
+ }
+
+ qCWarning(lcPermissions) << "Unknown permission status" << status << "detected in" << self;
+ return Qt::PermissionStatus::Denied;
+}
+
+- (QStringList)usageDescriptionsFor:(QPermission)permission
+{
+ Q_UNUSED(permission);
+ return { "NSCalendarsUsageDescription" };
+}
+
+- (void)requestPermission:(QPermission)permission withCallback:(PermissionCallback)callback
+{
+ if (!self.eventStore) {
+ // Note: Creating the EKEventStore results in warnings in the
+ // console about "An error occurred in the persistent store".
+ // This seems like a EventKit API bug.
+ self.eventStore = [[EKEventStore new] autorelease];
+ }
+
+ [self.eventStore requestAccessToEntityType:EKEntityTypeEvent
+ completion:^(BOOL granted, NSError * _Nullable error) {
+ Q_UNUSED(granted); // We use status instead
+ // Permission denied will result in an error, which we don't
+ // want to report/log, so we ignore the error and just report
+ // the status.
+ Q_UNUSED(error);
+
+ callback([self currentStatus]);
+ }
+ ];
+}
+
+@end
+
+#include "moc_qdarwinpermissionplugin_p_p.cpp"
diff --git a/src/corelib/platform/darwin/qdarwinpermissionplugin_camera.mm b/src/corelib/platform/darwin/qdarwinpermissionplugin_camera.mm
new file mode 100644
index 0000000000..51c517d6f3
--- /dev/null
+++ b/src/corelib/platform/darwin/qdarwinpermissionplugin_camera.mm
@@ -0,0 +1,42 @@
+// Copyright (C) 2022 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 "qdarwinpermissionplugin_p_p.h"
+
+#include <AVFoundation/AVFoundation.h>
+
+QT_DEFINE_PERMISSION_STATUS_CONVERTER(AVAuthorizationStatus);
+
+#ifndef BUILDING_PERMISSION_REQUEST
+
+@implementation QDarwinCameraPermissionHandler
+- (Qt::PermissionStatus)checkPermission:(QPermission)permission
+{
+ const auto status = [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo];
+ return nativeStatusToQtStatus(status);
+}
+
+- (QStringList)usageDescriptionsFor:(QPermission)permission
+{
+ Q_UNUSED(permission);
+ return { "NSCameraUsageDescription" };
+}
+@end
+
+#include "moc_qdarwinpermissionplugin_p_p.cpp"
+
+#else // Building request
+
+@implementation QDarwinCameraPermissionHandler (Request)
+- (void)requestPermission:(QPermission)permission withCallback:(PermissionCallback)callback
+{
+ [AVCaptureDevice requestAccessForMediaType:AVMediaTypeVideo completionHandler:^(BOOL granted)
+ {
+ Q_UNUSED(granted); // We use status instead
+ const auto status = [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo];
+ callback(nativeStatusToQtStatus(status));
+ }];
+}
+@end
+
+#endif // BUILDING_PERMISSION_REQUEST
diff --git a/src/corelib/platform/darwin/qdarwinpermissionplugin_contacts.mm b/src/corelib/platform/darwin/qdarwinpermissionplugin_contacts.mm
new file mode 100644
index 0000000000..3221b6dc1d
--- /dev/null
+++ b/src/corelib/platform/darwin/qdarwinpermissionplugin_contacts.mm
@@ -0,0 +1,58 @@
+// Copyright (C) 2022 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 "qdarwinpermissionplugin_p_p.h"
+
+#include <Contacts/Contacts.h>
+
+QT_DEFINE_PERMISSION_STATUS_CONVERTER(CNAuthorizationStatus);
+
+@interface QDarwinContactsPermissionHandler ()
+@property (nonatomic, retain) CNContactStore *contactStore;
+@end
+
+@implementation QDarwinContactsPermissionHandler
+- (Qt::PermissionStatus)checkPermission:(QPermission)permission
+{
+ Q_UNUSED(permission);
+ return [self currentStatus];
+}
+
+- (Qt::PermissionStatus)currentStatus
+{
+ const auto status = [CNContactStore authorizationStatusForEntityType:CNEntityTypeContacts];
+ return nativeStatusToQtStatus(status);
+}
+
+- (QStringList)usageDescriptionsFor:(QPermission)permission
+{
+ Q_UNUSED(permission);
+ return { "NSContactsUsageDescription" };
+}
+
+- (void)requestPermission:(QPermission)permission withCallback:(PermissionCallback)callback
+{
+ if (!self.contactStore) {
+ // Note: Creating the CNContactStore results in warnings in the
+ // console about "Attempted to register account monitor for types
+ // client is not authorized to access", mentioning CardDAV, LDAP,
+ // and Exchange. This seems like a Contacts API bug.
+ self.contactStore = [[CNContactStore new] autorelease];
+ }
+
+ [self.contactStore requestAccessForEntityType:CNEntityTypeContacts
+ completionHandler:^(BOOL granted, NSError * _Nullable error) {
+ Q_UNUSED(granted); // We use status instead
+ // Permission denied will result in an error, which we don't
+ // want to report/log, so we ignore the error and just report
+ // the status.
+ Q_UNUSED(error);
+
+ callback([self currentStatus]);
+ }
+ ];
+}
+
+@end
+
+#include "moc_qdarwinpermissionplugin_p_p.cpp"
diff --git a/src/corelib/platform/darwin/qdarwinpermissionplugin_location.mm b/src/corelib/platform/darwin/qdarwinpermissionplugin_location.mm
new file mode 100644
index 0000000000..1d32c0fcac
--- /dev/null
+++ b/src/corelib/platform/darwin/qdarwinpermissionplugin_location.mm
@@ -0,0 +1,263 @@
+// Copyright (C) 2022 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 "qdarwinpermissionplugin_p_p.h"
+
+#include <deque>
+
+#include <CoreLocation/CoreLocation.h>
+
+@interface QDarwinLocationPermissionHandler () <CLLocationManagerDelegate>
+@property (nonatomic, retain) CLLocationManager *manager;
+@end
+
+Q_LOGGING_CATEGORY(lcLocationPermission, "qt.permissions.location");
+
+void warmUpLocationServices()
+{
+ // After creating a CLLocationManager the authorizationStatus
+ // will initially be kCLAuthorizationStatusNotDetermined. The
+ // status will then update to an actual status if the app was
+ // previously authorized/denied once the location services
+ // do some initial book-keeping in the background. By kicking
+ // off a CLLocationManager early on here, we ensure that by
+ // the time the user calls checkPermission the authorization
+ // status has been resolved.
+ qCDebug(lcLocationPermission) << "Warming up location services";
+ [[CLLocationManager new] release];
+}
+
+Q_CONSTRUCTOR_FUNCTION(warmUpLocationServices);
+
+struct PermissionRequest
+{
+ QPermission permission;
+ PermissionCallback callback;
+};
+
+@implementation QDarwinLocationPermissionHandler {
+ std::deque<PermissionRequest> m_requests;
+}
+
+- (instancetype)init
+{
+ if ((self = [super init])) {
+ // The delegate callbacks will come in on the thread that
+ // the CLLocationManager is created on, and we want those
+ // to come in on the main thread, so we defer creation
+ // of the manger until requestPermission, where we know
+ // we are on the main thread.
+ self.manager = nil;
+ }
+
+ return self;
+}
+
+- (Qt::PermissionStatus)checkPermission:(QPermission)permission
+{
+ const auto locationPermission = *permission.value<QLocationPermission>();
+
+ auto status = [self authorizationStatus:locationPermission];
+ if (status != Qt::PermissionStatus::Granted)
+ return status;
+
+ return [self accuracyAuthorization:locationPermission];
+}
+
+- (Qt::PermissionStatus)authorizationStatus:(QLocationPermission)permission
+{
+ NSString *bundleIdentifier = NSBundle.mainBundle.bundleIdentifier;
+ if (!bundleIdentifier || !bundleIdentifier.length) {
+ qCWarning(lcLocationPermission) << "Missing bundle identifier"
+ << "in Info.plist. Can not use location permissions.";
+ return Qt::PermissionStatus::Denied;
+ }
+
+#if defined(Q_OS_VISIONOS)
+ if (permission.availability() == QLocationPermission::Always)
+ return Qt::PermissionStatus::Denied;
+#endif
+
+ auto status = [self authorizationStatus];
+ switch (status) {
+ case kCLAuthorizationStatusRestricted:
+ case kCLAuthorizationStatusDenied:
+ return Qt::PermissionStatus::Denied;
+ case kCLAuthorizationStatusNotDetermined:
+ return Qt::PermissionStatus::Undetermined;
+#if !defined(Q_OS_VISIONOS)
+ case kCLAuthorizationStatusAuthorizedAlways:
+ return Qt::PermissionStatus::Granted;
+#endif
+#if defined(Q_OS_IOS) || defined(Q_OS_VISIONOS)
+ case kCLAuthorizationStatusAuthorizedWhenInUse:
+ if (permission.availability() == QLocationPermission::Always)
+ return Qt::PermissionStatus::Denied;
+ return Qt::PermissionStatus::Granted;
+#endif
+ }
+
+ qCWarning(lcPermissions) << "Unknown permission status" << status << "detected in" << self;
+ return Qt::PermissionStatus::Denied;
+}
+
+- (CLAuthorizationStatus)authorizationStatus
+{
+ if (self.manager) {
+ if (@available(macOS 11, iOS 14, *))
+ return self.manager.authorizationStatus;
+ }
+
+ return QT_IGNORE_DEPRECATIONS(CLLocationManager.authorizationStatus);
+}
+
+- (Qt::PermissionStatus)accuracyAuthorization:(QLocationPermission)permission
+{
+ auto status = CLAccuracyAuthorizationReducedAccuracy;
+ if (@available(macOS 11, iOS 14, *))
+ status = self.manager.accuracyAuthorization;
+
+ switch (status) {
+ case CLAccuracyAuthorizationFullAccuracy:
+ return Qt::PermissionStatus::Granted;
+ case CLAccuracyAuthorizationReducedAccuracy:
+ if (permission.accuracy() == QLocationPermission::Approximate)
+ return Qt::PermissionStatus::Granted;
+ else
+ return Qt::PermissionStatus::Denied;
+ }
+
+ qCWarning(lcPermissions) << "Unknown accuracy status" << status << "detected in" << self;
+ return Qt::PermissionStatus::Denied;
+}
+
+- (QStringList)usageDescriptionsFor:(QPermission)permission
+{
+#if defined(Q_OS_MACOS)
+ return { "NSLocationUsageDescription" };
+#else // iOS 11 and above
+ QStringList usageDescriptions = { "NSLocationWhenInUseUsageDescription" };
+ const auto locationPermission = *permission.value<QLocationPermission>();
+ if (locationPermission.availability() == QLocationPermission::Always)
+ usageDescriptions << "NSLocationAlwaysAndWhenInUseUsageDescription";
+ return usageDescriptions;
+#endif
+}
+
+- (void)requestPermission:(QPermission)permission withCallback:(PermissionCallback)callback
+{
+ const bool requestAlreadyInFlight = !m_requests.empty();
+
+ m_requests.push_back({ permission, callback });
+
+ if (requestAlreadyInFlight) {
+ qCDebug(lcLocationPermission).nospace() << "Already processing "
+ << m_requests.front().permission << ". Deferring request";
+ } else {
+ [self requestQueuedPermission];
+ }
+}
+
+- (void)requestQueuedPermission
+{
+ Q_ASSERT(!m_requests.empty());
+ const auto permission = m_requests.front().permission;
+
+ qCDebug(lcLocationPermission) << "Requesting" << permission;
+
+ if (!self.manager) {
+ self.manager = [[CLLocationManager new] autorelease];
+ self.manager.delegate = self;
+ }
+
+ const auto locationPermission = *permission.value<QLocationPermission>();
+ switch (locationPermission.availability()) {
+ case QLocationPermission::WhenInUse:
+ // The documentation specifies that requestWhenInUseAuthorization can
+ // only be called when the current authorization status is undetermined.
+ switch ([self authorizationStatus]) {
+ case kCLAuthorizationStatusNotDetermined:
+ [self.manager requestWhenInUseAuthorization];
+ break;
+ default:
+ [self deliverResult];
+ }
+ break;
+ case QLocationPermission::Always:
+#if defined(Q_OS_VISIONOS)
+ [self deliverResult]; // Not supported
+#else
+ // The documentation specifies that requestAlwaysAuthorization can only
+ // be called when the current authorization status is either undetermined,
+ // or authorized when in use.
+ switch ([self authorizationStatus]) {
+ case kCLAuthorizationStatusNotDetermined:
+ [self.manager requestAlwaysAuthorization];
+ break;
+#ifdef Q_OS_IOS
+ case kCLAuthorizationStatusAuthorizedWhenInUse:
+ // Unfortunately when asking for AlwaysAuthorization when in
+ // the WhenInUse state, to "upgrade" the permission, the iOS
+ // location system will not give us a callback if the user
+ // denies the request, leaving us hanging without a way to
+ // respond to the permission request.
+ qCWarning(lcLocationPermission) << "QLocationPermission::WhenInUse"
+ << "can not be upgraded to QLocationPermission::Always on iOS."
+ << "Please request QLocationPermission::Always directly.";
+ Q_FALLTHROUGH();
+#endif
+ default:
+ [self deliverResult];
+ }
+#endif
+ break;
+ }
+}
+
+- (void)locationManager:(CLLocationManager *)manager didChangeAuthorizationStatus:(CLAuthorizationStatus)status
+{
+ qCDebug(lcLocationPermission) << "Processing authorization"
+ << "update with status" << status;
+
+ if (m_requests.empty()) {
+ qCDebug(lcLocationPermission) << "No requests in flight. Ignoring.";
+ return;
+ }
+
+ if (status == kCLAuthorizationStatusNotDetermined) {
+ // Initializing a CLLocationManager will result in an initial
+ // callback to the delegate even before we've requested any
+ // location permissions. Normally we would ignore this callback
+ // due to the request queue check above, but if this callback
+ // comes in after the application has requested a permission
+ // we don't want to report the undetermined status, but rather
+ // wait for the actual result to come in.
+ qCDebug(lcLocationPermission) << "Ignoring delegate callback"
+ << "with status kCLAuthorizationStatusNotDetermined";
+ return;
+ }
+
+ [self deliverResult];
+}
+
+- (void)deliverResult
+{
+ auto request = m_requests.front();
+ m_requests.pop_front();
+
+ auto status = [self checkPermission:request.permission];
+ qCDebug(lcLocationPermission) << "Result for"
+ << request.permission << "was" << status;
+
+ request.callback(status);
+
+ if (!m_requests.empty()) {
+ qCDebug(lcLocationPermission) << "Still have"
+ << m_requests.size() << "deferred request(s)";
+ [self requestQueuedPermission];
+ }
+}
+
+@end
+
+#include "moc_qdarwinpermissionplugin_p_p.cpp"
diff --git a/src/corelib/platform/darwin/qdarwinpermissionplugin_microphone.mm b/src/corelib/platform/darwin/qdarwinpermissionplugin_microphone.mm
new file mode 100644
index 0000000000..5dc434309d
--- /dev/null
+++ b/src/corelib/platform/darwin/qdarwinpermissionplugin_microphone.mm
@@ -0,0 +1,42 @@
+// Copyright (C) 2022 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 "qdarwinpermissionplugin_p_p.h"
+
+#include <AVFoundation/AVFoundation.h>
+
+QT_DEFINE_PERMISSION_STATUS_CONVERTER(AVAuthorizationStatus);
+
+#ifndef BUILDING_PERMISSION_REQUEST
+
+@implementation QDarwinMicrophonePermissionHandler
+- (Qt::PermissionStatus)checkPermission:(QPermission)permission
+{
+ const auto status = [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeAudio];
+ return nativeStatusToQtStatus(status);
+}
+
+- (QStringList)usageDescriptionsFor:(QPermission)permission
+{
+ Q_UNUSED(permission);
+ return { "NSMicrophoneUsageDescription" };
+}
+@end
+
+#include "moc_qdarwinpermissionplugin_p_p.cpp"
+
+#else // Building request
+
+@implementation QDarwinMicrophonePermissionHandler (Request)
+- (void)requestPermission:(QPermission)permission withCallback:(PermissionCallback)callback
+{
+ [AVCaptureDevice requestAccessForMediaType:AVMediaTypeAudio completionHandler:^(BOOL granted)
+ {
+ Q_UNUSED(granted); // We use status instead
+ const auto status = [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeAudio];
+ callback(nativeStatusToQtStatus(status));
+ }];
+}
+@end
+
+#endif // BUILDING_PERMISSION_REQUEST
diff --git a/src/corelib/platform/darwin/qdarwinpermissionplugin_p.h b/src/corelib/platform/darwin/qdarwinpermissionplugin_p.h
new file mode 100644
index 0000000000..03530133ad
--- /dev/null
+++ b/src/corelib/platform/darwin/qdarwinpermissionplugin_p.h
@@ -0,0 +1,58 @@
+// Copyright (C) 2022 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
+
+#ifndef QDARWINPERMISSIONPLUGIN_P_H
+#define QDARWINPERMISSIONPLUGIN_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. This header file may change
+// from version to version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <QtCore/qnamespace.h>
+#include <QtCore/private/qpermissions_p.h>
+#include <QtCore/private/qcore_mac_p.h>
+
+#if defined(__OBJC__)
+#include <Foundation/NSObject.h>
+#endif
+
+QT_USE_NAMESPACE
+
+using namespace QPermissions::Private;
+
+#if defined(__OBJC__)
+Q_CORE_EXPORT
+#endif
+QT_DECLARE_NAMESPACED_OBJC_INTERFACE(QDarwinPermissionHandler, NSObject
+- (Qt::PermissionStatus)checkPermission:(QPermission)permission;
+- (void)requestPermission:(QPermission)permission withCallback:(PermissionCallback)callback;
+- (QStringList)usageDescriptionsFor:(QPermission)permission;
+)
+
+QT_BEGIN_NAMESPACE
+
+class Q_CORE_EXPORT QDarwinPermissionPlugin : public QPermissionPlugin
+{
+ Q_OBJECT
+public:
+ QDarwinPermissionPlugin(QDarwinPermissionHandler *handler);
+ ~QDarwinPermissionPlugin();
+
+ Qt::PermissionStatus checkPermission(const QPermission &permission) override;
+ void requestPermission(const QPermission &permission, const PermissionCallback &callback) override;
+
+private:
+ Q_SLOT void permissionUpdated(Qt::PermissionStatus status, const PermissionCallback &callback);
+ bool verifyUsageDescriptions(const QPermission &permission);
+ QDarwinPermissionHandler *m_handler = nullptr;
+};
+
+QT_END_NAMESPACE
+
+#endif // QDARWINPERMISSIONPLUGIN_P_H
diff --git a/src/corelib/platform/darwin/qdarwinpermissionplugin_p_p.h b/src/corelib/platform/darwin/qdarwinpermissionplugin_p_p.h
new file mode 100644
index 0000000000..649af06507
--- /dev/null
+++ b/src/corelib/platform/darwin/qdarwinpermissionplugin_p_p.h
@@ -0,0 +1,104 @@
+// Copyright (C) 2022 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
+
+#ifndef QDARWINPERMISSIONPLUGIN_P_P_H
+#define QDARWINPERMISSIONPLUGIN_P_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. This header file may change
+// from version to version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#if !defined(QT_DARWIN_PERMISSION_PLUGIN)
+#error "This header should only be included from permission plugins"
+#endif
+
+#include <QtCore/qnamespace.h>
+#include <QtCore/private/qpermissions_p.h>
+#include <QtCore/private/qcore_mac_p.h>
+
+#include "qdarwinpermissionplugin_p.h"
+
+using namespace QPermissions::Private;
+
+#ifndef QT_JOIN
+#define QT_JOIN_IMPL(A, B) A ## B
+#define QT_JOIN(A, B) QT_JOIN_IMPL(A, B)
+#endif
+
+#define PERMISSION_PLUGIN_NAME(SUFFIX) \
+ QT_JOIN(QT_JOIN(QT_JOIN( \
+ QDarwin, QT_DARWIN_PERMISSION_PLUGIN), Permission), SUFFIX)
+
+#define PERMISSION_PLUGIN_CLASSNAME PERMISSION_PLUGIN_NAME(Plugin)
+#define PERMISSION_PLUGIN_HANDLER PERMISSION_PLUGIN_NAME(Handler)
+
+QT_DECLARE_NAMESPACED_OBJC_INTERFACE(
+ PERMISSION_PLUGIN_HANDLER,
+ QDarwinPermissionHandler
+)
+
+QT_BEGIN_NAMESPACE
+
+class Q_CORE_EXPORT PERMISSION_PLUGIN_CLASSNAME : public QDarwinPermissionPlugin
+{
+ Q_OBJECT
+ Q_PLUGIN_METADATA(
+ IID QPermissionPluginInterface_iid
+ FILE "QDarwin" QT_STRINGIFY(QT_DARWIN_PERMISSION_PLUGIN) "PermissionPlugin.json")
+public:
+ PERMISSION_PLUGIN_CLASSNAME()
+ : QDarwinPermissionPlugin([[PERMISSION_PLUGIN_HANDLER alloc] init])
+ {}
+};
+
+QT_END_NAMESPACE
+
+// Request
+#if defined(BUILDING_PERMISSION_REQUEST)
+extern "C" void PERMISSION_PLUGIN_NAME(Request)() {}
+#endif
+
+// -------------------------------------------------------
+
+namespace {
+template <typename NativeStatus>
+struct NativeStatusHelper;
+
+template <typename NativeStatus>
+Qt::PermissionStatus nativeStatusToQtStatus(NativeStatus status)
+{
+ using Converter = NativeStatusHelper<NativeStatus>;
+ switch (status) {
+ case Converter::Authorized:
+ return Qt::PermissionStatus::Granted;
+ case Converter::Denied:
+ case Converter::Restricted:
+ return Qt::PermissionStatus::Denied;
+ case Converter::Undetermined:
+ return Qt::PermissionStatus::Undetermined;
+ }
+ qCWarning(lcPermissions) << "Unknown permission status" << status << "detected in"
+ << QT_STRINGIFY(QT_DARWIN_PERMISSION_PLUGIN);
+ return Qt::PermissionStatus::Denied;
+}
+} // namespace
+
+#define QT_DEFINE_PERMISSION_STATUS_CONVERTER(NativeStatus) \
+namespace { template<> \
+struct NativeStatusHelper<NativeStatus> \
+{\
+ enum { \
+ Authorized = NativeStatus##Authorized, \
+ Denied = NativeStatus##Denied, \
+ Restricted = NativeStatus##Restricted, \
+ Undetermined = NativeStatus##NotDetermined \
+ }; \
+}; }
+
+#endif // QDARWINPERMISSIONPLUGIN_P_P_H