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
4#include "qpermissions.h"
5#include "qpermissions_p.h"
6#include "qhashfunctions.h"
7
8#include <QtCore/qshareddata.h>
9#include <QtCore/qdebug.h>
10
11QT_BEGIN_NAMESPACE
12
13Q_LOGGING_CATEGORY(lcPermissions, "qt.permissions", QtWarningMsg);
14
15/*!
16 \page permissions.html
17 \title Application Permissions
18 \brief Managing application permissions
19
20 Many features of today's devices and operating systems can have
21 significant privacy, security, and performance implications if
22 misused. It's therefore increasingly common for platforms to
23 require explicit consent from the user before accessing these
24 features.
25
26 The Qt permission APIs allow the application to check or request
27 permission for such features in a cross platform manner.
28
29 \section1 Usage
30
31 A feature that commonly requires user consent is access to the
32 microphone of the device. An application for recording voice
33 memos would perhaps look something like this initially:
34
35 \code
36 void VoiceMemoWidget::onRecordingInitiated()
37 {
38 m_microphone->startRecording();
39 }
40 \endcode
41
42 To ensure this application works well on platforms that
43 require user consent for microphone access we would extend
44 it like this:
45
46 \code
47 void VoiceMemoWidget::onRecordingInitiated()
48 {
49 QMicrophonePermission microphonePermission;
50 switch (qApp->checkPermission(microphonePermission)) {
51 case Qt::PermissionStatus::Undetermined:
52 qApp->requestPermission(microphonePermission, this,
53 &VoiceMemoWidget::onRecordingInitiated);
54 return;
55 case Qt::PermissionStatus::Denied:
56 m_permissionInstructionsDialog->show();
57 return;
58 case Qt::PermissionStatus::Granted:
59 m_microphone->startRecording();
60 }
61 }
62 \endcode
63
64 We first check if we already know the status of the microphone permission.
65 If we don't we initiate a permission request to determine the current
66 status, which will potentially ask the user for consent. We connect the
67 result of the request to the slot we're already in, so that we get another
68 chance at evaluating the permission status.
69
70 Once the permission status is known, either because we had been granted or
71 denied permission at an earlier time, or after getting the result back from
72 the request we just initiated, we redirect the user to a dialog explaining
73 why we can not record voice memos at this time (if the permission was denied),
74 or proceed to using the microphone (if permission was granted).
75
76 \note On \macOS and iOS permissions can currently only be requested for
77 GUI applications.
78
79 \section2 Declaring Permissions
80
81 Some platforms require that the permissions you request are declared
82 up front at build time.
83
84 \section3 Apple platforms
85 \target apple-usage-description
86
87 Each permission you request must be accompanied by a so called
88 \e {usage description} string in the application's
89 \l{Information Property List Files}{\c Info.plist}
90 file, describing why the application needs to access the given
91 permission. For example:
92
93 \badcode
94 <key>NSMicrophoneUsageDescription</key>
95 <string>The microphone is used to record voice memos.</string>
96 \endcode
97
98 The relevant usage description keys are described in the documentation
99 for each permission type.
100
101 To ensure the relevant permission backend is included with your
102 application, please \l{Information Property List Files}
103 {point the build system to your custom \c Info.plist}.
104
105 \sa {Information Property List Files}.
106
107 \section3 Android
108 \target android-uses-permission
109
110 Each permission you request must be accompanied by a \c uses-permission
111 entry in the application's \c AndroidManifest.xml file. For example:
112
113 \badcode
114 <manifest ...>
115 <uses-permission android:name="android.permission.RECORD_AUDIO"/>
116 </manifest>
117 \endcode
118
119 The relevant permission names are described in the documentation
120 for each permission type.
121
122 \sa {Qt Creator: Editing Manifest Files}.
123
124 \section1 Available Permissions
125
126 The following permissions types are available:
127
128 \annotatedlist permissions
129
130 \section1 Best Practices
131
132 To ensure the best possible user experience for the end user we recommend
133 adopting the following best practices for managing application permissions:
134
135 \list
136
137 \li Request the minimal set of permissions needed. For example, if you only
138 need access to the microphone, do \e not request camera permission just in case.
139 Use the properties of individual permission types to limit the permission scope
140 even further, for example \l{QContactsPermission::setAccessMode()}
141 to request read only access.
142
143 \li Request permissions in response to specific actions by the user. For example,
144 defer requesting microphone permission until the user presses the button to record
145 audio. Associating the permission request to a specific action gives the user a clearer
146 context of why the permission is needed. Do \e not request all needed permission on
147 startup.
148
149 \li Present extra context and explanation if needed. Sometimes the action by the user
150 is not enough context. Consider presenting an explanation-dialog after the user has
151 initiated the action, but before requesting the permission, so the user is aware of
152 what's about to happen when the system permission dialog subsequently pops up.
153
154 \li Be transparent and explicit about why permissions are needed. In explanation
155 dialogs and usage descriptions, be transparent about why the particular permission
156 is needed for your application to provide a specific feature, so users can make
157 informed decisions.
158
159 \li Account for denied permissions. The permissions you request may be denied
160 for various reasons. You should always account for this situation, by gracefully
161 degrading the experience of your application, and presenting clear explanations
162 the user about the situation.
163
164 \li Never request permissions from a library. The request of permissions should
165 be done as close as possible to the user, where the information needed to make
166 good decisions on the points above is available. Libraries can check permissions,
167 to ensure they have the prerequisites for doing their work, but if the permission
168 is undetermined or denied this should be reflected through the library's API,
169 so that the application in turn can request the necessary permissions.
170
171 \endlist
172*/
173
174
175/*!
176 \class QPermission
177 \inmodule QtCore
178 \inheaderfile QPermissions
179 \since 6.5
180 \brief An opaque wrapper of a typed permission.
181
182 The QPermission class is an opaque wrapper of a \l{typed permission},
183 used when checking or requesting permissions. You do not need to construct
184 this type explicitly, as the type is automatically used when checking or
185 requesting permissions:
186
187 \code
188 qApp->checkPermission(QCameraPermission{});
189 \endcode
190
191 When requesting permissions, the given functor will
192 be passed an instance of a QPermission, which can be used
193 to check the result of the request:
194
195 \code
196 qApp->requestPermission(QCameraPermission{}, [](const QPermission &permission) {
197 if (permission.status() == Qt::PermissionStatus:Granted)
198 takePhoto();
199 });
200 \endcode
201
202 To inspect the properties of the original, typed permission,
203 use the \l {QPermission::}{value()} function:
204
205 \code
206 QLocationPermission locationPermission;
207 locationPermission.setAccuracy(QLocationPermission::Precise);
208 qApp->requestPermission(locationPermission, this, &LocationWidget::permissionUpdated);
209 \endcode
210
211 \code
212 void LocationWidget::permissionUpdated(const QPermission &permission)
213 {
214 if (permission.status() != Qt::PermissionStatus:Granted)
215 return;
216 auto locationPermission = permission.value<QLocationPermission>();
217 if (!locationPermission || locationPermission->accuracy() != QLocationPermission::Precise)
218 return;
219 updatePreciseLocation();
220 }
221 \endcode
222
223 \target typed permission
224 \section2 Typed Permissions
225
226 The following permissions are available:
227
228 \annotatedlist permissions
229
230 \sa {Application Permissions}
231*/
232
233/*!
234 \fn template <typename T, QPermission::if_permission<T>> QPermission::QPermission(const T &type)
235
236 Constructs a permission from the given \l{typed permission} \a type.
237
238 You do not need to construct this type explicitly, as the type is automatically
239 used when checking or requesting permissions.
240
241 This constructor participates in overload resolution only if \c T is one of
242 the \l{typed permission} classes:
243
244 \annotatedlist permissions
245*/
246
247/*!
248 \fn template <typename T, QPermission::if_permission<T>> std::optional<T> QPermission::value() const
249
250 Returns the \l{typed permission} of type \c T, or \c{std::nullopt} if this
251 QPermission object doesn't contain one.
252
253 Use type() for dynamically choosing which typed permission to request.
254
255 This function participates in overload resolution only if \c T is one of
256 the \l{typed permission} classes:
257
258 \annotatedlist permissions
259*/
260
261/*!
262 \fn Qt::PermissionStatus QPermission::status() const
263 Returns the status of the permission.
264*/
265
266/*!
267 \fn QMetaType QPermission::type() const
268 Returns the type of the permission.
269*/
270
271/*
272 \internal
273*/
274const void *QPermission::data(QMetaType requestedType) const
275{
276 const auto actualType = type();
277 if (requestedType != actualType)
278 return nullptr;
279 return m_data.data();
280}
281
282// check alignof(AlignmentCheck) instead of alignof(void*), in case
283// pointers have different alignment inside structs:
284struct AlignmentCheck { void *p; };
285
286#define QT_PERMISSION_IMPL_COMMON(ClassName) \
287 /* Class##Private is unused until we need it: */ \
288 static_assert(sizeof(ClassName) == sizeof(void*), \
289 "You have added too many members to " #ClassName "::ShortData. " \
290 "Decrease their size or switch to using a d-pointer."); \
291 static_assert(alignof(ClassName) == alignof(AlignmentCheck), \
292 "You have added members to " #ClassName "::ShortData that are overaligned. " \
293 "Decrease their alignment or switch to using a d-pointer."); \
294 ClassName::ClassName(const ClassName &other) noexcept = default; \
295 ClassName::~ClassName() = default; \
296 ClassName &ClassName::operator=(const ClassName &other) noexcept = default; \
297 ClassName::ClassName() \
298 /* impl supplied by caller */
299
300
301/*!
302 \class QCameraPermission
303 \brief Access the camera for taking pictures or videos.
304
305 \section1 Requirements
306
307 \include permissions.qdocinc begin-usage-declarations
308 \row
309 \li Apple
310 \li \l{apple-usage-description}{Usage description}
311 \li \c NSCameraUsageDescription
312 \row
313 \li Android
314 \li \l{android-uses-permission}{\c{uses-permission}}
315 \li \c android.permission.CAMERA
316 \include permissions.qdocinc end-usage-declarations
317
318 \include permissions.qdocinc permission-metadata
319*/
320
321QT_PERMISSION_IMPL_COMMON(QCameraPermission)
322 : u{} // stateless, atm
323{}
324
325/*!
326 \class QMicrophonePermission
327 \brief Access the microphone for monitoring or recording sound.
328
329 \section1 Requirements
330
331 \include permissions.qdocinc begin-usage-declarations
332 \row
333 \li Apple
334 \li \l{apple-usage-description}{Usage description}
335 \li \c NSMicrophoneUsageDescription
336 \row
337 \li Android
338 \li \l{android-uses-permission}{\c{uses-permission}}
339 \li \c android.permission.RECORD_AUDIO
340 \include permissions.qdocinc end-usage-declarations
341
342 \include permissions.qdocinc permission-metadata
343*/
344
345QT_PERMISSION_IMPL_COMMON(QMicrophonePermission)
346 : u{} // stateless, atm
347{}
348
349/*!
350 \class QBluetoothPermission
351 \brief Access Bluetooth peripherals.
352
353 \section1 Requirements
354
355 \include permissions.qdocinc begin-usage-declarations
356 \row
357 \li Apple
358 \li \l{apple-usage-description}{Usage description}
359 \li \c NSBluetoothAlwaysUsageDescription
360 \row
361 \li Android
362 \li \l{android-uses-permission}{\c{uses-permission}}
363 \li Up to Android 11 (API Level < 31):
364 \list
365 \li \c android.permission.BLUETOOTH
366 \li \c android.permission.ACCESS_FINE_LOCATION
367 \endlist
368
369 Starting from Android 12 (API Level >= 31):
370 \list
371 \li \c android.permission.BLUETOOTH_ADVERTISE
372 \li \c android.permission.BLUETOOTH_CONNECT
373 \li \c android.permission.BLUETOOTH_SCAN
374 \endlist
375 \include permissions.qdocinc end-usage-declarations
376
377 \note Since Qt 6.8.1, the ACCESS_FINE_LOCATION permission is no longer
378 requested if API Level >= 31. This
379 \l {Android Bluetooth Permissions}{may limit some Bluetooth scan results}.
380 Users needing these results need
381 to request the location permission separately (see
382 \l {QLocationPermission::Precise}{precise location}) and ensure that
383 \c {BLUETOOTH_SCAN} permission doesn't have the
384 \c {android:usesPermissionFlags="neverForLocation"} attribute set.
385 For setting and customizing permissions in the application manifest,
386 \l {Qt Permissions and Features}{see this guide}.
387
388 \include permissions.qdocinc permission-metadata
389*/
390
391QT_PERMISSION_IMPL_COMMON(QBluetoothPermission)
392 : u{ShortData{.mode: CommunicationMode::Default, .reserved: {}}}
393{}
394
395/*!
396 \enum QBluetoothPermission::CommunicationMode
397 \since 6.6
398
399 This enum is used to control the allowed Bluetooth communication modes.
400
401 \value Access Allow this device to access other Bluetooth devices. This
402 includes scanning for nearby devices and connecting to them.
403 \value Advertise Allow other Bluetooth devices to discover this device.
404 \value Default This configuration is used by default.
405
406 \note The fine-grained permissions are currently supported only on
407 Android 12 and newer. On older Android versions, as well as on Apple
408 operating systems, any mode results in full Bluetooth access.
409*/
410
411/*!
412 \since 6.6
413
414 Sets the allowed Bluetooth communication modes to \a modes.
415
416 \note A default-constructed instance of \l {QBluetoothPermission::}
417 {CommunicationModes} has no sense, so an attempt to set such a mode will
418 raise a \c {qWarning()} and fall back to using the
419 \l {QBluetoothPermission::}{Default} mode.
420*/
421void QBluetoothPermission::setCommunicationModes(CommunicationModes modes)
422{
423 if (modes == CommunicationModes{}) {
424 qCWarning(lcPermissions, "QBluetoothPermission: trying to set an invalid empty mode. "
425 "Falling back to CommunicationMode::Default.");
426 u.data.mode = Default;
427 } else {
428 u.data.mode = static_cast<CommunicationMode>(modes.toInt());
429 }
430}
431
432/*!
433 \since 6.6
434
435 Returns the allowed Bluetooth communication modes.
436*/
437QBluetoothPermission::CommunicationModes QBluetoothPermission::communicationModes() const
438{
439 return u.data.mode;
440}
441
442/*!
443 \class QLocationPermission
444 \brief Access the user's location.
445
446 By default the request is for approximate accuracy,
447 and only while the application is in use. Use
448 setAccuracy() and/or setAvailability() to override
449 the default.
450
451 \section1 Requirements
452
453 \include permissions.qdocinc begin-usage-declarations
454 \row
455 \li \macos
456 \li \l{apple-usage-description}{Usage description}
457 \li \c NSLocationUsageDescription
458 \row
459 \li iOS
460 \li \l{apple-usage-description}{Usage description}
461 \li \c NSLocationWhenInUseUsageDescription, and
462 \c NSLocationAlwaysAndWhenInUseUsageDescription if requesting
463 QLocationPermission::Always
464 \row
465 \li Android
466 \li \l{android-uses-permission}{\c{uses-permission}}
467 \li \list
468 \li \c android.permission.ACCESS_FINE_LOCATION for QLocationPermission::Precise
469 \li \c android.permission.ACCESS_COARSE_LOCATION for QLocationPermission::Approximate
470 \li \c android.permission.ACCESS_BACKGROUND_LOCATION for QLocationPermission::Always
471 \endlist
472 \note QLocationPermission::Always \c uses-permission string has
473 to be combined with one or both of QLocationPermission::Precise
474 and QLocationPermission::Approximate strings.
475 \include permissions.qdocinc end-usage-declarations
476
477 \include permissions.qdocinc permission-metadata
478*/
479
480QT_PERMISSION_IMPL_COMMON(QLocationPermission)
481 : u{ShortData{.accuracy: Accuracy::Approximate, .availability: Availability::WhenInUse, .reserved: {}}}
482{}
483
484/*!
485 \enum QLocationPermission::Accuracy
486
487 This enum is used to control the accuracy of the location data.
488
489 \value Approximate An approximate location is requested.
490 \value Precise A precise location is requested.
491*/
492
493/*!
494 \enum QLocationPermission::Availability
495
496 This enum is used to control the availability of the location data.
497
498 \value WhenInUse The location is only available only when the
499 application is in use.
500 \value Always The location is available at all times, including when
501 the application is in the background.
502*/
503
504/*!
505 Sets the desired \a accuracy of the request.
506*/
507void QLocationPermission::setAccuracy(Accuracy accuracy)
508{
509 u.data.accuracy = accuracy;
510}
511
512/*!
513 Returns the accuracy of the request.
514*/
515QLocationPermission::Accuracy QLocationPermission::accuracy() const
516{
517 return u.data.accuracy;
518}
519
520/*!
521 Sets the desired \a availability of the request.
522*/
523void QLocationPermission::setAvailability(Availability availability)
524{
525 u.data.availability = availability;
526}
527
528/*!
529 Returns the availability of the request.
530*/
531QLocationPermission::Availability QLocationPermission::availability() const
532{
533 return u.data.availability;
534}
535
536/*!
537 \class QContactsPermission
538 \brief Access the user's contacts.
539
540 By default the request is for read-only access.
541 Use setAccessMode() to override the default.
542
543 \section1 Requirements
544
545 \include permissions.qdocinc begin-usage-declarations
546 \row
547 \li Apple
548 \li \l{apple-usage-description}{Usage description}
549 \li \c NSContactsUsageDescription
550 \row
551 \li Android
552 \li \l{android-uses-permission}{\c{uses-permission}}
553 \li \c android.permission.READ_CONTACTS. \c android.permission.WRITE_CONTACTS if
554 QContactsPermission::accessMode() is set to AccessMode::ReadWrite.
555 \include permissions.qdocinc end-usage-declarations
556
557 \include permissions.qdocinc permission-metadata
558*/
559
560/*!
561 \enum QContactsPermission::AccessMode
562
563 This enum is used to control access to the contacts data.
564
565 \value ReadOnly Read-only access to the contacts data (the default).
566 \value ReadWrite Read and write access to the contacts data.
567
568 \sa setAccessMode, accessMode
569*/
570
571QT_PERMISSION_IMPL_COMMON(QContactsPermission)
572 : u{ShortData{.mode: AccessMode::ReadOnly, .reserved: {}}}
573{}
574
575/*!
576 Sets whether the request is for read-write (\a mode == AccessMode::ReadOnly) or
577 read-only (\a mode == AccessMode::ReadOnly) access to the contacts.
578*/
579void QContactsPermission::setAccessMode(AccessMode mode)
580{
581 u.data.mode = mode;
582}
583
584/*!
585 Returns AccessMode::ReadWrite when the request is for read-write and
586 AccessMode::ReadOnly when it is for read-only access to the contacts.
587*/
588QContactsPermission::AccessMode QContactsPermission::accessMode() const
589{
590 return u.data.mode;
591}
592
593/*!
594 \class QCalendarPermission
595 \brief Access the user's calendar.
596
597 By default the request is for read-only access.
598 Use setAccessMode() to override the default.
599
600 \section1 Requirements
601
602 \include permissions.qdocinc begin-usage-declarations
603 \row
604 \li Apple
605 \li \l{apple-usage-description}{Usage description}
606 \li \c NSCalendarsUsageDescription
607 \row
608 \li Android
609 \li \l{android-uses-permission}{\c{uses-permission}}
610 \li \c android.permission.READ_CALENDAR. \c android.permission.WRITE_CALENDAR if
611 QCalendarPermission::accessMode() is set to AccessMode::ReadWrite.
612 \include permissions.qdocinc end-usage-declarations
613
614 \include permissions.qdocinc permission-metadata
615*/
616
617/*!
618 \enum QCalendarPermission::AccessMode
619
620 This enum is used to control access to the calendar data.
621
622 \value ReadOnly Read-only access to the calendar data (the default).
623 \value ReadWrite Read and write access to the calendar data.
624
625 \sa setAccessMode, accessMode
626*/
627
628QT_PERMISSION_IMPL_COMMON(QCalendarPermission)
629 : u{ShortData{.mode: AccessMode::ReadOnly, .reserved: {}}}
630{}
631
632/*!
633 Sets whether the request is for read-write (\a mode == AccessMode::ReadOnly) or
634 read-only (\a mode == AccessMode::ReadOnly) access to the calendar.
635*/
636void QCalendarPermission::setAccessMode(AccessMode mode)
637{
638 u.data.mode = mode;
639}
640
641/*!
642 Returns AccessMode::ReadWrite when the request is for read-write and
643 AccessMode::ReadOnly when it is for read-only access to the calendar.
644*/
645QCalendarPermission::AccessMode QCalendarPermission::accessMode() const
646{
647 return u.data.mode;
648}
649
650/*!
651 * \internal
652*/
653
654QPermissionPlugin::~QPermissionPlugin() = default;
655
656#ifndef QT_NO_DEBUG_STREAM
657QDebug operator<<(QDebug debug, const QPermission &permission)
658{
659 const auto verbosity = debug.verbosity();
660 QDebugStateSaver saver(debug);
661 debug.nospace().setVerbosity(0);
662 if (verbosity >= QDebug::DefaultVerbosity)
663 debug << permission.type().name() << "(";
664 debug << permission.status();
665 if (verbosity >= QDebug::DefaultVerbosity)
666 debug << ")";
667 return debug;
668}
669#endif
670
671#undef QT_PERMISSION_IMPL_COMMON
672
673#if !defined(Q_OS_DARWIN) && !defined(Q_OS_ANDROID) && !defined(Q_OS_WASM)
674// Default backend for platforms without a permission implementation.
675// Always returns Granted, to match behavior when not using permission APIs
676// https://bugreports.qt.io/browse/QTBUG-90498?focusedCommentId=725085#comment-725085
677namespace QPermissions::Private
678{
679 Qt::PermissionStatus checkPermission(const QPermission &permission)
680 {
681 qCDebug(lcPermissions) << "No permission backend on this platform."
682 << "Optimistically returning Granted for" << permission;
683 return Qt::PermissionStatus::Granted;
684 }
685
686 void requestPermission(const QPermission &permission, const PermissionCallback &callback)
687 {
688 qCDebug(lcPermissions) << "No permission backend on this platform."
689 << "Optimistically returning Granted for" << permission;
690 callback(Qt::PermissionStatus::Granted);
691 }
692}
693#endif
694
695QT_END_NAMESPACE
696
697#include "moc_qpermissions.cpp"
698

Provided by KDAB

Privacy Policy
Learn to use CMake with our Intro Training
Find out more

source code of qtbase/src/corelib/kernel/qpermissions.cpp