Long live QPermissions!
Many features of today's devices and operating systems can have significant privacy, security, and performance implications if misused. It's therefore increasingly common for platforms to require explicit consent from the user before accessing these features. The Qt permission APIs allow the application to check or request permission for such features in a cross platform manner. The check is always synchronous, and can be used in both library and application code, from any thread. The request is asynchronous, and should be initiated from application code on the main thread. The result of the request can be delivered to lambdas, standalone functions, or regular member functions such as slots, with an optional context parameter to manage the lifetime of the request. Individual permissions are distinct types, not enum values, and can be added and extended at a later point. Task-number: QTBUG-90498 Done-with: Timur Pocheptsov <timur.pocheptsov@qt.io> Done-with: Volker Hilsheimer <volker.hilsheimer@qt.io> Done-with: Mårten Nordheim <marten.nordheim@qt.io> Change-Id: I821380bbe56bbc0178cb43e6cabbc99fdbd1235e Reviewed-by: Timur Pocheptsov <timur.pocheptsov@qt.io>
This commit is contained in:
parent
62a4fe434d
commit
33cf9d32da
28
examples/corelib/permissions/CMakeLists.txt
Normal file
28
examples/corelib/permissions/CMakeLists.txt
Normal file
@ -0,0 +1,28 @@
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
project(permissions LANGUAGES CXX)
|
||||
|
||||
set(CMAKE_AUTOMOC ON)
|
||||
|
||||
if(NOT DEFINED INSTALL_EXAMPLESDIR)
|
||||
set(INSTALL_EXAMPLESDIR "examples")
|
||||
endif()
|
||||
|
||||
set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/corelib/permissions")
|
||||
|
||||
find_package(Qt6 REQUIRED COMPONENTS Core Gui Widgets)
|
||||
|
||||
qt_add_executable(permissions
|
||||
main.cpp
|
||||
)
|
||||
|
||||
target_link_libraries(permissions PUBLIC
|
||||
Qt::Core
|
||||
Qt::Gui
|
||||
Qt::Widgets
|
||||
)
|
||||
|
||||
install(TARGETS permissions
|
||||
RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}"
|
||||
BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}"
|
||||
LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}"
|
||||
)
|
83
examples/corelib/permissions/main.cpp
Normal file
83
examples/corelib/permissions/main.cpp
Normal file
@ -0,0 +1,83 @@
|
||||
// Copyright (C) 2022 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
#include <QtCore/qmetaobject.h>
|
||||
#include <QtWidgets/qapplication.h>
|
||||
#include <QtWidgets/qwidget.h>
|
||||
#include <QtWidgets/qpushbutton.h>
|
||||
#include <QtWidgets/qlayout.h>
|
||||
#include <QtWidgets/qmessagebox.h>
|
||||
|
||||
QT_REQUIRE_CONFIG(permissions);
|
||||
#include <QtCore/qpermissions.h>
|
||||
|
||||
class PermissionWidget : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit PermissionWidget(QWidget *parent = nullptr) : QWidget(parent)
|
||||
{
|
||||
QVBoxLayout *layout = new QVBoxLayout(this);
|
||||
|
||||
static const QPermission permissions[] = {
|
||||
QCameraPermission{},
|
||||
QMicrophonePermission{},
|
||||
QBluetoothPermission{},
|
||||
QContactsPermission{},
|
||||
QCalendarPermission{},
|
||||
QLocationPermission{}
|
||||
};
|
||||
|
||||
for (auto permission : permissions) {
|
||||
auto permissionName = QString::fromLatin1(permission.name());
|
||||
QPushButton *button = new QPushButton(permissionName.sliced(1, permissionName.length() - 11));
|
||||
connect(button, &QPushButton::clicked, this, &PermissionWidget::buttonClicked);
|
||||
button->setProperty("permission", QVariant::fromValue(permission));
|
||||
layout->addWidget(button);
|
||||
}
|
||||
|
||||
QPalette pal = palette();
|
||||
pal.setBrush(QPalette::Window, QGradient(QGradient::HappyAcid));
|
||||
setPalette(pal);
|
||||
}
|
||||
|
||||
private:
|
||||
void buttonClicked()
|
||||
{
|
||||
auto *button = static_cast<QPushButton*>(sender());
|
||||
|
||||
auto permission = button->property("permission").value<QPermission>();
|
||||
Q_ASSERT(permission.type().isValid());
|
||||
|
||||
switch (qApp->checkPermission(permission)) {
|
||||
case Qt::PermissionStatus::Undetermined:
|
||||
qApp->requestPermission(permission, this,
|
||||
[this, button](const QPermission &permission) {
|
||||
emit button->clicked(); // Try again
|
||||
}
|
||||
);
|
||||
return;
|
||||
case Qt::PermissionStatus::Denied:
|
||||
QMessageBox::warning(this, button->text(),
|
||||
tr("Permission is needed to use %1. Please grant permission "\
|
||||
"to this application in the system settings.").arg(button->text()));
|
||||
return;
|
||||
case Qt::PermissionStatus::Granted:
|
||||
break; // Proceed
|
||||
}
|
||||
|
||||
// All good, can use the feature
|
||||
QMessageBox::information(this, button->text(),
|
||||
tr("Accessing %1").arg(button->text()));
|
||||
}
|
||||
};
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
QApplication app(argc, argv);
|
||||
PermissionWidget widget;
|
||||
widget.show();
|
||||
return app.exec();
|
||||
}
|
||||
|
||||
#include "main.moc"
|
@ -1159,6 +1159,11 @@ qt_internal_extend_target(Core CONDITION QT_FEATURE_mimetype
|
||||
mimetypes/qmimetypeparser.cpp mimetypes/qmimetypeparser_p.h
|
||||
)
|
||||
|
||||
qt_internal_extend_target(Core CONDITION QT_FEATURE_permissions
|
||||
SOURCES
|
||||
kernel/qpermissions.cpp kernel/qpermissions.h kernel/qpermissions_p.h
|
||||
)
|
||||
|
||||
#### Keys ignored in scope 171:.:mimetypes:mimetypes/mimetypes.pri:QT_FEATURE_mimetype:
|
||||
# MIME_DATABASE = "mimetypes/mime/packages/freedesktop.org.xml"
|
||||
# OTHER_FILES = "$$MIME_DATABASE"
|
||||
|
@ -968,6 +968,12 @@ qt_feature("poll-exit-on-error" PUBLIC
|
||||
PURPOSE "Exit on error instead of just printing the error code and continue."
|
||||
)
|
||||
qt_feature_definition("poll-exit-on-error" "QT_POLL_EXIT_ON_ERROR")
|
||||
qt_feature("permissions" PUBLIC
|
||||
SECTION "Utilities"
|
||||
LABEL "Application permissions"
|
||||
PURPOSE "Provides support for requesting user permission to access restricted data or APIs"
|
||||
DISABLE ON
|
||||
)
|
||||
qt_configure_add_summary_section(NAME "Qt Core")
|
||||
qt_configure_add_summary_entry(ARGS "backtrace")
|
||||
qt_configure_add_summary_entry(ARGS "doubleconversion")
|
||||
@ -998,6 +1004,7 @@ qt_configure_add_summary_entry(
|
||||
ARGS "forkfd_pidfd"
|
||||
CONDITION LINUX
|
||||
)
|
||||
qt_configure_add_summary_entry(ARGS "permissions")
|
||||
qt_configure_end_summary_section() # end of "Qt Core" section
|
||||
qt_configure_add_report_entry(
|
||||
TYPE NOTE
|
||||
|
@ -1,2 +1,3 @@
|
||||
#include <QtCore/QtCore>
|
||||
#include "../../platform/android/qandroidextras_p.h"
|
||||
#include "../../kernel/qpermissions.h"
|
||||
|
@ -34,7 +34,8 @@ headerdirs += ..
|
||||
|
||||
sourcedirs += .. \
|
||||
../../tools/androiddeployqt \
|
||||
../../android/templates
|
||||
../../android/templates \
|
||||
src/includes
|
||||
|
||||
exampledirs += \
|
||||
../ \
|
||||
|
51
src/corelib/doc/src/includes/permissions.qdocinc
Normal file
51
src/corelib/doc/src/includes/permissions.qdocinc
Normal file
@ -0,0 +1,51 @@
|
||||
// Copyright (C) 2022 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only
|
||||
|
||||
//! [requestPermission-functor]
|
||||
When the request is ready, \a functor will be called as
|
||||
\c {functor(const QPermission &permission)}, with
|
||||
\c permission describing the result of the request.
|
||||
//! [requestPermission-functor]
|
||||
|
||||
//! [requestPermission-postamble]
|
||||
If the user explicitly grants the application the requested \a permission,
|
||||
or the \a permission is known to not require user authorization on the given
|
||||
platform, the status will be Qt::PermissionStatus::Granted.
|
||||
|
||||
If the user explicitly denies the application the requested \a permission,
|
||||
or the \a permission is known to not be accessible or applicable to applications
|
||||
on the given platform, the status will be Qt::PermissionStatus::Denied.
|
||||
|
||||
The result of a request will never be Qt::PermissionStatus::Undetermined.
|
||||
|
||||
\note Permissions can only be requested from the main thread.
|
||||
//! [requestPermission-postamble]
|
||||
|
||||
//! [permission-metadata]
|
||||
\inmodule QtCore
|
||||
\inheaderfile QPermissions
|
||||
\ingroup permissions
|
||||
\since 6.5
|
||||
\sa QPermission,
|
||||
QCoreApplication::requestPermission(),
|
||||
QCoreApplication::checkPermission(),
|
||||
{Application Permissions}
|
||||
//! [permission-metadata]
|
||||
|
||||
//! [begin-usage-declarations]
|
||||
To request this permission at runtime, the following platform
|
||||
specific usage declarations have to be made at build time:
|
||||
|
||||
\table
|
||||
\header
|
||||
\li Platform
|
||||
\li Type
|
||||
\li
|
||||
//! [begin-usage-declarations]
|
||||
|
||||
//! [end-usage-declarations]
|
||||
\endtable
|
||||
|
||||
Please see the individual usage declaration types for how
|
||||
to add them to your project.
|
||||
//! [end-usage-declarations]
|
@ -108,6 +108,7 @@
|
||||
|
||||
#define QT_FEATURE_commandlineparser 1
|
||||
#define QT_FEATURE_settings -1
|
||||
#define QT_FEATURE_permissions -1
|
||||
|
||||
#define QT_NO_TEMPORARYFILE
|
||||
|
||||
|
@ -1708,6 +1708,12 @@ namespace Qt {
|
||||
PassThrough
|
||||
};
|
||||
|
||||
enum class PermissionStatus {
|
||||
Undetermined,
|
||||
Granted,
|
||||
Denied,
|
||||
};
|
||||
|
||||
// QTBUG-48701
|
||||
enum ReturnByValueConstant { ReturnByValue }; // ### Qt 7: Remove me
|
||||
|
||||
@ -1803,6 +1809,7 @@ namespace Qt {
|
||||
Q_ENUM_NS(ChecksumType)
|
||||
Q_ENUM_NS(HighDpiScaleFactorRoundingPolicy)
|
||||
Q_ENUM_NS(TabFocusBehavior)
|
||||
Q_ENUM_NS(PermissionStatus)
|
||||
#endif // Q_DOC
|
||||
|
||||
}
|
||||
|
@ -3295,6 +3295,31 @@
|
||||
\value PassThrough Don't round.
|
||||
*/
|
||||
|
||||
/*!
|
||||
\enum Qt::PermissionStatus
|
||||
|
||||
This enum describes the possible statuses of a permissions.
|
||||
|
||||
\value Undetermined
|
||||
The permission status is not yet known. Permission should be requested
|
||||
via QCoreApplication::requestPermission() to determine the actual status.
|
||||
This status will never be the result of requesting a permission.
|
||||
|
||||
\value Granted
|
||||
The user has explicitly granted the application the permission,
|
||||
or the permission is known to not require user authorization on
|
||||
the given platform.
|
||||
|
||||
\value Denied
|
||||
The user has explicitly denied the application the requested permission,
|
||||
or the permission is known to not be accessible or applicable to applications
|
||||
on the given platform.
|
||||
|
||||
\since 6.5
|
||||
\sa QCoreApplication::requestPermission(), QCoreApplication::checkPermission(),
|
||||
{Application Permissions}
|
||||
*/
|
||||
|
||||
/*!
|
||||
\enum Qt::ReturnByValueConstant
|
||||
\since 5.15
|
||||
|
@ -42,6 +42,10 @@
|
||||
#include <private/qlocking_p.h>
|
||||
#include <private/qhooks_p.h>
|
||||
|
||||
#if QT_CONFIG(permissions)
|
||||
#include <private/qpermissions_p.h>
|
||||
#endif
|
||||
|
||||
#ifndef QT_NO_QOBJECT
|
||||
#if defined(Q_OS_UNIX)
|
||||
# if defined(Q_OS_DARWIN)
|
||||
@ -2659,6 +2663,125 @@ QString QCoreApplication::applicationVersion()
|
||||
return coreappdata() ? coreappdata()->applicationVersion : QString();
|
||||
}
|
||||
|
||||
#if QT_CONFIG(permissions) || defined(Q_QDOC)
|
||||
|
||||
/*!
|
||||
Checks the status of the given \a permission
|
||||
|
||||
If the result is Qt::PermissionStatus::Undetermined then permission should be
|
||||
requested via requestPermission() to determine the user's intent.
|
||||
|
||||
\since 6.5
|
||||
\sa requestPermission(), {Application Permissions}
|
||||
*/
|
||||
Qt::PermissionStatus QCoreApplication::checkPermission(const QPermission &permission)
|
||||
{
|
||||
return QPermissions::Private::checkPermission(permission);
|
||||
}
|
||||
|
||||
/*!
|
||||
\fn template<typename Functor> void QCoreApplication::requestPermission(
|
||||
const QPermission &permission, Functor functor)
|
||||
|
||||
Requests the given \a permission.
|
||||
|
||||
\include permissions.qdocinc requestPermission-functor
|
||||
|
||||
The \a functor can be a free-standing or static member function:
|
||||
|
||||
\code
|
||||
qApp->requestPermission(QCameraPermission{}, &permissionUpdated);
|
||||
\endcode
|
||||
|
||||
or a lambda:
|
||||
|
||||
\code
|
||||
qApp->requestPermission(QCameraPermission{}, [](const QPermission &permission) {
|
||||
});
|
||||
\endcode
|
||||
|
||||
\include permissions.qdocinc requestPermission-postamble
|
||||
|
||||
\since 6.5
|
||||
\sa checkPermission(), {Application Permissions}
|
||||
*/
|
||||
|
||||
/*!
|
||||
\fn template<typename Functor> void QCoreApplication::requestPermission(
|
||||
const QPermission &permission, const QObject *context,
|
||||
Functor functor)
|
||||
|
||||
Requests the given \a permission, in the context of \a context.
|
||||
|
||||
\include permissions.qdocinc requestPermission-functor
|
||||
|
||||
The \a functor can be a free-standing or static member function:
|
||||
|
||||
\code
|
||||
qApp->requestPermission(QCameraPermission{}, context, &permissionUpdated);
|
||||
\endcode
|
||||
|
||||
a lambda:
|
||||
|
||||
\code
|
||||
qApp->requestPermission(QCameraPermission{}, context, [](const QPermission &permission) {
|
||||
});
|
||||
\endcode
|
||||
|
||||
or a slot in the \a context object:
|
||||
|
||||
\code
|
||||
qApp->requestPermission(QCameraPermission{}, this, &CamerWidget::permissionUpdated);
|
||||
\endcode
|
||||
|
||||
If \a context is destroyed before the request completes,
|
||||
the \a functor will not be called.
|
||||
|
||||
\include permissions.qdocinc requestPermission-postamble
|
||||
|
||||
\since 6.5
|
||||
\overload
|
||||
\sa checkPermission(), {Application Permissions}
|
||||
*/
|
||||
|
||||
/*!
|
||||
\internal
|
||||
|
||||
Called by the various requestPermission overloads to perform the request.
|
||||
|
||||
Calls the functor encapsulated in the \a slotObj in the given \a context
|
||||
(which may be \c nullptr).
|
||||
*/
|
||||
void QCoreApplication::requestPermission(const QPermission &requestedPermission,
|
||||
QtPrivate::QSlotObjectBase *slotObj, const QObject *context)
|
||||
{
|
||||
if (QThread::currentThread() != QCoreApplicationPrivate::mainThread()) {
|
||||
qWarning(lcPermissions, "Permissions can only be requested from the GUI (main) thread");
|
||||
return;
|
||||
}
|
||||
|
||||
Q_ASSERT(slotObj);
|
||||
|
||||
QPermissions::Private::requestPermission(requestedPermission, [=](Qt::PermissionStatus status) {
|
||||
Q_ASSERT_X(status != Qt::PermissionStatus::Undetermined, "QPermission",
|
||||
"QCoreApplication::requestPermission() should never return Undetermined");
|
||||
if (status == Qt::PermissionStatus::Undetermined)
|
||||
status = Qt::PermissionStatus::Denied;
|
||||
|
||||
if (QCoreApplication::self) {
|
||||
QPermission permission = requestedPermission;
|
||||
permission.m_status = status;
|
||||
|
||||
void *argv[] = { nullptr, &permission };
|
||||
slotObj->call(const_cast<QObject*>(context), argv);
|
||||
}
|
||||
|
||||
slotObj->destroyIfLastRef();
|
||||
});
|
||||
}
|
||||
|
||||
#endif // QT_CONFIG(permissions)
|
||||
|
||||
#if QT_CONFIG(library)
|
||||
|
||||
Q_GLOBAL_STATIC(QRecursiveMutex, libraryPathMutex)
|
||||
|
@ -33,6 +33,10 @@ class QPostEventList;
|
||||
class QAbstractEventDispatcher;
|
||||
class QAbstractNativeEventFilter;
|
||||
|
||||
#if QT_CONFIG(permissions) || defined(Q_QDOC)
|
||||
class QPermission;
|
||||
#endif
|
||||
|
||||
#define qApp QCoreApplication::instance()
|
||||
|
||||
class Q_CORE_EXPORT QCoreApplication
|
||||
@ -107,6 +111,74 @@ public:
|
||||
static QString applicationFilePath();
|
||||
static qint64 applicationPid() Q_DECL_CONST_FUNCTION;
|
||||
|
||||
#if QT_CONFIG(permissions) || defined(Q_QDOC)
|
||||
Qt::PermissionStatus checkPermission(const QPermission &permission);
|
||||
|
||||
# ifdef Q_QDOC
|
||||
template <typename Functor>
|
||||
void requestPermission(const QPermission &permission, Functor functor);
|
||||
template <typename Functor>
|
||||
void requestPermission(const QPermission &permission, const QObject *context, Functor functor);
|
||||
# else
|
||||
template <typename Slot> // requestPermission to a QObject slot
|
||||
void requestPermission(const QPermission &permission,
|
||||
const typename QtPrivate::FunctionPointer<Slot>::Object *receiver, Slot slot)
|
||||
{
|
||||
using CallbackSignature = QtPrivate::FunctionPointer<void (*)(QPermission)>;
|
||||
using SlotSignature = QtPrivate::FunctionPointer<Slot>;
|
||||
|
||||
static_assert(int(SlotSignature::ArgumentCount) <= int(CallbackSignature::ArgumentCount),
|
||||
"Slot requires more arguments than what can be provided.");
|
||||
static_assert((QtPrivate::CheckCompatibleArguments<typename CallbackSignature::Arguments, typename SlotSignature::Arguments>::value),
|
||||
"Slot arguments are not compatible (must be QPermission)");
|
||||
|
||||
auto slotObj = new QtPrivate::QSlotObject<Slot, typename SlotSignature::Arguments, void>(slot);
|
||||
requestPermission(permission, slotObj, receiver);
|
||||
}
|
||||
|
||||
// requestPermission to a functor or function pointer (with context)
|
||||
template <typename Func, std::enable_if_t<
|
||||
!QtPrivate::FunctionPointer<Func>::IsPointerToMemberFunction
|
||||
&& !std::is_same<const char *, Func>::value, bool> = true>
|
||||
void requestPermission(const QPermission &permission, const QObject *context, Func func)
|
||||
{
|
||||
using CallbackSignature = QtPrivate::FunctionPointer<void (*)(QPermission)>;
|
||||
constexpr int MatchingArgumentCount = QtPrivate::ComputeFunctorArgumentCount<
|
||||
Func, CallbackSignature::Arguments>::Value;
|
||||
|
||||
static_assert(MatchingArgumentCount == 0
|
||||
|| MatchingArgumentCount == CallbackSignature::ArgumentCount,
|
||||
"Functor arguments are not compatible (must be QPermission)");
|
||||
|
||||
QtPrivate::QSlotObjectBase *slotObj = nullptr;
|
||||
if constexpr (MatchingArgumentCount == CallbackSignature::ArgumentCount) {
|
||||
slotObj = new QtPrivate::QFunctorSlotObject<Func, 1,
|
||||
typename CallbackSignature::Arguments, void>(std::move(func));
|
||||
} else {
|
||||
slotObj = new QtPrivate::QFunctorSlotObject<Func, 0,
|
||||
typename QtPrivate::List_Left<void, 0>::Value, void>(std::move(func));
|
||||
}
|
||||
|
||||
requestPermission(permission, slotObj, context);
|
||||
}
|
||||
|
||||
// requestPermission to a functor or function pointer (without context)
|
||||
template <typename Func, std::enable_if_t<
|
||||
!QtPrivate::FunctionPointer<Func>::IsPointerToMemberFunction
|
||||
&& !std::is_same<const char *, Func>::value, bool> = true>
|
||||
void requestPermission(const QPermission &permission, Func func)
|
||||
{
|
||||
requestPermission(permission, nullptr, std::move(func));
|
||||
}
|
||||
|
||||
private:
|
||||
void requestPermission(const QPermission &permission,
|
||||
QtPrivate::QSlotObjectBase *slotObj, const QObject *context);
|
||||
public:
|
||||
# endif // Q_QDOC
|
||||
|
||||
#endif // QT_CONFIG(permission)
|
||||
|
||||
#if QT_CONFIG(library)
|
||||
static void setLibraryPaths(const QStringList &);
|
||||
static QStringList libraryPaths();
|
||||
|
493
src/corelib/kernel/qpermissions.cpp
Normal file
493
src/corelib/kernel/qpermissions.cpp
Normal file
@ -0,0 +1,493 @@
|
||||
// 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 "qpermissions.h"
|
||||
#include "qpermissions_p.h"
|
||||
#include "qhashfunctions.h"
|
||||
|
||||
#include <QtCore/qshareddata.h>
|
||||
#include <QtCore/qdebug.h>
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
|
||||
Q_LOGGING_CATEGORY(lcPermissions, "qt.permissions", QtWarningMsg);
|
||||
|
||||
/*!
|
||||
\page permissions.html
|
||||
\title Application Permissions
|
||||
\brief Managing application permissions
|
||||
|
||||
Many features of today's devices and operating systems can have
|
||||
significant privacy, security, and performance implications if
|
||||
misused. It's therefore increasingly common for platforms to
|
||||
require explicit consent from the user before accessing these
|
||||
features.
|
||||
|
||||
The Qt permission APIs allow the application to check or request
|
||||
permission for such features in a cross platform manner.
|
||||
|
||||
\section1 Usage
|
||||
|
||||
A feature that commonly requires user consent is access to the
|
||||
microphone of the device. An application for recording voice
|
||||
memos would perhaps look something like this initially:
|
||||
|
||||
\code
|
||||
void VoiceMemoWidget::onRecordingInitiated()
|
||||
{
|
||||
m_microphone->startRecording();
|
||||
}
|
||||
\endcode
|
||||
|
||||
To ensure this application works well on platforms that
|
||||
require user consent for microphone access we would extend
|
||||
it like this:
|
||||
|
||||
\code
|
||||
void VoiceMemoWidget::onRecordingInitiated()
|
||||
{
|
||||
#if QT_CONFIG(permissions)
|
||||
QMicrophonePermission microphonePermission;
|
||||
switch (qApp->checkPermission(microphonePermission)) {
|
||||
case Qt::PermissionStatus::Undetermined:
|
||||
qApp->requestPermission(microphonePermission, this
|
||||
&VoiceMemoWidget::onRecordingInitiated);
|
||||
return;
|
||||
case Qt::PermissionStatus::Denied:
|
||||
m_permissionInstructionsDialog->show();
|
||||
return;
|
||||
case Qt::PermissionStatus::Granted:
|
||||
break; // Proceed
|
||||
}
|
||||
#endif
|
||||
m_microphone->startRecording();
|
||||
}
|
||||
\endcode
|
||||
|
||||
We first check if we already know the status of the microphone permission.
|
||||
If we don't we initiate a permission request to determine the current
|
||||
status, which will potentially ask the user for consent. We connect the
|
||||
result of the request to the slot we're already in, so that we get another
|
||||
chance at evaluating the permission status.
|
||||
|
||||
Once the permission status is known, either because we had been granted or
|
||||
denied permission at an earlier time, or after getting the result back from
|
||||
the request we just initiated, we redirect the user to a dialog explaining
|
||||
why we can not record voice memos at this time (if the permission was denied),
|
||||
or proceed to using the microphone (if permission was granted).
|
||||
|
||||
The use of the \c{QT_CONFIG(permissions)} macro ensures that the code
|
||||
will work as before on platforms where permissions are not available.
|
||||
|
||||
\section2 Declaring Permissions
|
||||
|
||||
Some platforms require that the permissions you request are declared
|
||||
up front at build time.
|
||||
|
||||
\section3 Apple platforms
|
||||
\target apple-usage-description
|
||||
|
||||
Each permission you request must be accompanied by a so called
|
||||
\e {usage description} string in the application's \c Info.plist
|
||||
file, describing why the application needs to access the given
|
||||
permission. For example:
|
||||
|
||||
\badcode
|
||||
<key>NSMicrophoneUsageDescription</key>
|
||||
<string>The microphone is used to record voice memos.</string>
|
||||
\endcode
|
||||
|
||||
The relevant usage description keys are described in the documentation
|
||||
for each permission type.
|
||||
|
||||
\sa {Information Property List Files}.
|
||||
|
||||
\section3 Android
|
||||
\target android-uses-permission
|
||||
|
||||
Each permission you request must be accompanied by a \c uses-permission
|
||||
entry in the application's \c AndroidManifest.xml file. For example:
|
||||
|
||||
\badcode
|
||||
<manifest ...>
|
||||
<uses-permission android:name="android.permission.RECORD_AUDIO"/>
|
||||
</manifest>
|
||||
\endcode
|
||||
|
||||
The relevant permission names are described in the documentation
|
||||
for each permission type.
|
||||
|
||||
\sa {Qt Creator: Editing Manifest Files}.
|
||||
|
||||
\section1 Available Permissions
|
||||
|
||||
The following permissions types are available:
|
||||
|
||||
\annotatedlist permissions
|
||||
|
||||
\section1 Best Practices
|
||||
|
||||
To ensure the best possible user experience for the end user we recommend
|
||||
adopting the following best practices for managing application permissions:
|
||||
|
||||
\list
|
||||
|
||||
\li Request the minimal set of permissions needed. For example, if you only
|
||||
need access to the microphone, do \e not request camera permission just in case.
|
||||
Use the properties of individual permission types to limit the permission scope
|
||||
even further, for example QContactsPermission::setReadOnly() to request read
|
||||
only access.
|
||||
|
||||
\li Request permissions in response to specific actions by the user. For example,
|
||||
defer requesting microphone permission until the user presses the button to record
|
||||
audio. Associating the permission request to a specific action gives the user a clearer
|
||||
context of why the permission is needed. Do \e not request all needed permission on
|
||||
startup.
|
||||
|
||||
\li Present extra context and explanation if needed. Sometimes the action by the user
|
||||
is not enough context. Consider presenting an explanation-dialog after the user has
|
||||
initiated the action, but before requesting the permission, so the user is aware of
|
||||
what's about to happen when the system permission dialog subsequently pops up.
|
||||
|
||||
\li Be transparent and explicit about why permissions are needed. In explanation
|
||||
dialogs and usage descriptions, be transparent about why the particular permission
|
||||
is needed for your application to provide a specific feature, so users can make
|
||||
informed decisions.
|
||||
|
||||
\li Account for denied permissions. The permissions you request may be denied
|
||||
for various reasons. You should always account for this situation, by gracefully
|
||||
degrading the experience of your application, and presenting clear explanations
|
||||
the user about the situation.
|
||||
|
||||
\li Never request permissions from a library. The request of permissions should
|
||||
be done as close as possible to the user, where the information needed to make
|
||||
good decisions on the points above is available. Libraries can check permissions,
|
||||
to ensure they have the prerequisites for doing their work, but if the permission
|
||||
is undetermined or denied this should be reflected through the library's API,
|
||||
so that the application in turn can request the necessary permissions.
|
||||
|
||||
\endlist
|
||||
*/
|
||||
|
||||
|
||||
/*!
|
||||
\class QPermission
|
||||
\inmodule QtCore
|
||||
\inheaderfile QPermissions
|
||||
\brief An opaque wrapper of a typed permission.
|
||||
|
||||
The QPermission class is an opaque wrapper of a \l{typed permission},
|
||||
used when checking or requesting permissions. You do not need to construct
|
||||
this type explicitly, as the type is automatically used when checking or
|
||||
requesting permissions:
|
||||
|
||||
\code
|
||||
qApp->checkPermission(QCameraPermission{});
|
||||
\endcode
|
||||
|
||||
When requesting permissions, the given functor will
|
||||
be passed an instance of a QPermissions, which can be used
|
||||
to check the result of the request:
|
||||
|
||||
\code
|
||||
qApp->requestPermission(QCameraPermission{}, [](const QPermission &permission) {
|
||||
if (permission.status() == Qt::PermissionStatus:Granted)
|
||||
takePhoto();
|
||||
});
|
||||
\endcode
|
||||
|
||||
To inspect the properties of the original typed permission,
|
||||
use the data() function:
|
||||
|
||||
\code
|
||||
QLocationPermission locationPermission;
|
||||
locationPermission.setAccuracy(QLocationPermission::Precise);
|
||||
qApp->requestPermission(locationPermission, this, &LocationWidget::permissionUpdated);
|
||||
\endcode
|
||||
|
||||
\code
|
||||
void LocationWidget::permissionUpdated(const QPermission &permission)
|
||||
{
|
||||
if (permission.status() != Qt::PermissionStatus:Granted)
|
||||
return;
|
||||
auto locationPermission = permission.data<QLocationPermission>();
|
||||
if (locationPermission.accuracy() != QLocationPerission::Precise)
|
||||
return;
|
||||
updatePreciseLocation();
|
||||
}
|
||||
\endcode
|
||||
|
||||
\target typed permission
|
||||
\section2 Typed Permissions
|
||||
|
||||
The following permissions are available:
|
||||
|
||||
\annotatedlist permissions
|
||||
|
||||
\sa {Application Permissions}
|
||||
*/
|
||||
|
||||
/*!
|
||||
\fn template <typename Type> QPermission::QPermission(const Type &type)
|
||||
|
||||
Constructs a permission from the given \l{typed permission} \a type.
|
||||
|
||||
You do not need to construct this type explicitly, as the type is automatically
|
||||
used when checking or requesting permissions.
|
||||
*/
|
||||
|
||||
/*!
|
||||
\fn template <typename Type> Type QPermission::data() const
|
||||
|
||||
Returns the \l{typed permission} of type \c Type.
|
||||
|
||||
The type must match the type that was originally used to request
|
||||
the permission. Use type() for dynamically choosing which typed
|
||||
permission to request.
|
||||
*/
|
||||
|
||||
/*!
|
||||
Returns the status of the permission.
|
||||
*/
|
||||
Qt::PermissionStatus QPermission::status() const
|
||||
{
|
||||
return m_status;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the type of the permission.
|
||||
*/
|
||||
QMetaType QPermission::type() const
|
||||
{
|
||||
return m_data.metaType();
|
||||
}
|
||||
|
||||
#define QT_DEFINE_PERMISSION_SPECIAL_FUNCTIONS(ClassName) \
|
||||
ClassName::ClassName() : d(new ClassName##Private) {} \
|
||||
ClassName::ClassName(const ClassName &other) noexcept = default; \
|
||||
ClassName::ClassName(ClassName &&other) noexcept = default; \
|
||||
ClassName::~ClassName() noexcept = default; \
|
||||
ClassName &ClassName::operator=(const ClassName &other) noexcept = default;
|
||||
|
||||
/*!
|
||||
\class QCameraPermission
|
||||
\brief Access the camera for taking pictures or videos.
|
||||
|
||||
\section1 Requirements
|
||||
|
||||
\include permissions.qdocinc begin-usage-declarations
|
||||
\include permissions.qdocinc end-usage-declarations
|
||||
|
||||
\include permissions.qdocinc permission-metadata
|
||||
*/
|
||||
class QCameraPermissionPrivate : public QSharedData {};
|
||||
QT_DEFINE_PERMISSION_SPECIAL_FUNCTIONS(QCameraPermission)
|
||||
|
||||
/*!
|
||||
\class QMicrophonePermission
|
||||
\brief Access the microphone for monitoring or recording sound.
|
||||
|
||||
\section1 Requirements
|
||||
|
||||
\include permissions.qdocinc begin-usage-declarations
|
||||
|
||||
\include permissions.qdocinc end-usage-declarations
|
||||
|
||||
\include permissions.qdocinc permission-metadata
|
||||
*/
|
||||
class QMicrophonePermissionPrivate : public QSharedData {};
|
||||
QT_DEFINE_PERMISSION_SPECIAL_FUNCTIONS(QMicrophonePermission)
|
||||
|
||||
/*!
|
||||
\class QBluetoothPermission
|
||||
\brief Access Bluetooth peripherals.
|
||||
|
||||
\section1 Requirements
|
||||
|
||||
\include permissions.qdocinc begin-usage-declarations
|
||||
\include permissions.qdocinc end-usage-declarations
|
||||
|
||||
\include permissions.qdocinc permission-metadata
|
||||
*/
|
||||
class QBluetoothPermissionPrivate : public QSharedData {};
|
||||
QT_DEFINE_PERMISSION_SPECIAL_FUNCTIONS(QBluetoothPermission)
|
||||
|
||||
/*!
|
||||
\class QLocationPermission
|
||||
\brief Access the user's location.
|
||||
|
||||
By default the request is for approximate accuracy,
|
||||
and only while the application is in use. Use
|
||||
setAccuracy() and/or setAvailability() to override
|
||||
the default.
|
||||
|
||||
\section1 Requirements
|
||||
|
||||
\include permissions.qdocinc begin-usage-declarations
|
||||
\include permissions.qdocinc end-usage-declarations
|
||||
|
||||
\include permissions.qdocinc permission-metadata
|
||||
*/
|
||||
class QLocationPermissionPrivate : public QSharedData
|
||||
{
|
||||
public:
|
||||
using Accuracy = QLocationPermission::Accuracy;
|
||||
Accuracy accuracy = Accuracy::Approximate;
|
||||
|
||||
using Availability = QLocationPermission::Availability;
|
||||
Availability availability = Availability::WhenInUse;
|
||||
};
|
||||
|
||||
QT_DEFINE_PERMISSION_SPECIAL_FUNCTIONS(QLocationPermission)
|
||||
|
||||
/*!
|
||||
\enum QLocationPermission::Accuracy
|
||||
|
||||
This enum is used to control the accuracy of the location data.
|
||||
|
||||
\value Approximate An approximate location is requested.
|
||||
\value Precise A precise location is requested.
|
||||
*/
|
||||
|
||||
/*!
|
||||
\enum QLocationPermission::Availability
|
||||
|
||||
This enum is used to control the availability of the location data.
|
||||
|
||||
\value WhenInUse The location is only available only when the
|
||||
application is in use.
|
||||
\value Always The location is available at all times, including when
|
||||
the application is in the background.
|
||||
*/
|
||||
|
||||
/*!
|
||||
Sets the desired \a accuracy of the request.
|
||||
*/
|
||||
void QLocationPermission::setAccuracy(Accuracy accuracy)
|
||||
{
|
||||
d.detach();
|
||||
d->accuracy = accuracy;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the accuracy of the request.
|
||||
*/
|
||||
QLocationPermission::Accuracy QLocationPermission::accuracy() const
|
||||
{
|
||||
return d->accuracy;
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets the desired \a availability of the request.
|
||||
*/
|
||||
void QLocationPermission::setAvailability(Availability availability)
|
||||
{
|
||||
d.detach();
|
||||
d->availability = availability;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the availability of the request.
|
||||
*/
|
||||
QLocationPermission::Availability QLocationPermission::availability() const
|
||||
{
|
||||
return d->availability;
|
||||
}
|
||||
|
||||
/*!
|
||||
\class QContactsPermission
|
||||
\brief Access the user's contacts.
|
||||
|
||||
By default the request is for both read and write access.
|
||||
Use setReadOnly() to override the default.
|
||||
|
||||
\section1 Requirements
|
||||
|
||||
\include permissions.qdocinc begin-usage-declarations
|
||||
\include permissions.qdocinc end-usage-declarations
|
||||
|
||||
\include permissions.qdocinc permission-metadata
|
||||
*/
|
||||
class QContactsPermissionPrivate : public QSharedData
|
||||
{
|
||||
public:
|
||||
bool isReadOnly = false;
|
||||
};
|
||||
|
||||
QT_DEFINE_PERMISSION_SPECIAL_FUNCTIONS(QContactsPermission)
|
||||
|
||||
/*!
|
||||
Sets whether to \a enable read-only access to the contacts.
|
||||
*/
|
||||
void QContactsPermission::setReadOnly(bool enable)
|
||||
{
|
||||
d.detach();
|
||||
d->isReadOnly = enable;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns whether the request is for read-only access to the contacts.
|
||||
*/
|
||||
bool QContactsPermission::isReadOnly() const
|
||||
{
|
||||
return d->isReadOnly;
|
||||
}
|
||||
|
||||
/*!
|
||||
\class QCalendarPermission
|
||||
\brief Access the user's calendar.
|
||||
|
||||
By default the request is for both read and write access.
|
||||
Use setReadOnly() to override the default.
|
||||
|
||||
\section1 Requirements
|
||||
|
||||
\include permissions.qdocinc begin-usage-declarations
|
||||
\include permissions.qdocinc end-usage-declarations
|
||||
|
||||
\include permissions.qdocinc permission-metadata
|
||||
*/
|
||||
class QCalendarPermissionPrivate : public QSharedData
|
||||
{
|
||||
public:
|
||||
bool isReadOnly = false;
|
||||
};
|
||||
|
||||
QT_DEFINE_PERMISSION_SPECIAL_FUNCTIONS(QCalendarPermission)
|
||||
|
||||
/*!
|
||||
Sets whether to \a enable read-only access to the calendar.
|
||||
*/
|
||||
void QCalendarPermission::setReadOnly(bool enable)
|
||||
{
|
||||
d.detach();
|
||||
d->isReadOnly = enable;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns whether the request is for read-only access to the calendar.
|
||||
*/
|
||||
bool QCalendarPermission::isReadOnly() const
|
||||
{
|
||||
return d->isReadOnly;
|
||||
}
|
||||
|
||||
|
||||
#ifndef QT_NO_DEBUG_STREAM
|
||||
QDebug operator<<(QDebug debug, const QPermission &permission)
|
||||
{
|
||||
const auto verbosity = debug.verbosity();
|
||||
QDebugStateSaver saver(debug);
|
||||
debug.nospace().setVerbosity(0);
|
||||
if (verbosity >= QDebug::DefaultVerbosity)
|
||||
debug << permission.type().name() << "(";
|
||||
debug << permission.status();
|
||||
if (verbosity >= QDebug::DefaultVerbosity)
|
||||
debug << ")";
|
||||
return debug;
|
||||
}
|
||||
#endif
|
||||
|
||||
QT_END_NAMESPACE
|
||||
|
||||
#include "moc_qpermissions.cpp"
|
156
src/corelib/kernel/qpermissions.h
Normal file
156
src/corelib/kernel/qpermissions.h
Normal file
@ -0,0 +1,156 @@
|
||||
// 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 QPERMISSIONS_H
|
||||
#define QPERMISSIONS_H
|
||||
|
||||
#if 0
|
||||
#pragma qt_class(QPermissions)
|
||||
#endif
|
||||
|
||||
#include <QtCore/qglobal.h>
|
||||
#include <QtCore/qtmetamacros.h>
|
||||
#include <QtCore/qvariant.h>
|
||||
|
||||
#include <QtCore/qshareddata_impl.h>
|
||||
#include <QtCore/qtypeinfo.h>
|
||||
#include <QtCore/qmetatype.h>
|
||||
|
||||
#if !defined(Q_QDOC)
|
||||
QT_REQUIRE_CONFIG(permissions);
|
||||
#endif
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
|
||||
#ifndef QT_NO_DEBUG_STREAM
|
||||
class QDebug;
|
||||
#endif
|
||||
|
||||
struct QMetaObject;
|
||||
class QCoreApplication;
|
||||
|
||||
class Q_CORE_EXPORT QPermission
|
||||
{
|
||||
Q_GADGET
|
||||
|
||||
template <typename T, typename Enable = void>
|
||||
struct is_permission : public std::false_type {};
|
||||
|
||||
template <typename T>
|
||||
struct is_permission<T, typename T::QtPermissionHelper> : public std::true_type {};
|
||||
|
||||
public:
|
||||
explicit QPermission() = default;
|
||||
|
||||
#ifdef Q_QDOC
|
||||
template <typename Type>
|
||||
QPermission(const Type &type);
|
||||
#else
|
||||
template <typename T, std::enable_if_t<is_permission<T>::value, bool> = true>
|
||||
QPermission(const T &t) : m_data(QVariant::fromValue(t)) {}
|
||||
#endif
|
||||
|
||||
Qt::PermissionStatus status() const;
|
||||
|
||||
QMetaType type() const;
|
||||
|
||||
#ifdef Q_QDOC
|
||||
template <typename Type>
|
||||
Type data() const;
|
||||
#else
|
||||
template <typename T, std::enable_if_t<is_permission<T>::value, bool> = true>
|
||||
T data() const
|
||||
{
|
||||
auto requestedType = QMetaType::fromType<T>();
|
||||
if (type() != requestedType) {
|
||||
qWarning() << "Can not convert from" << type().name()
|
||||
<< "to" << requestedType.name();
|
||||
return T{};
|
||||
}
|
||||
return m_data.value<T>();
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifndef QT_NO_DEBUG_STREAM
|
||||
friend Q_CORE_EXPORT QDebug operator<<(QDebug debug, const QPermission &);
|
||||
#endif
|
||||
|
||||
private:
|
||||
Qt::PermissionStatus m_status = Qt::PermissionStatus::Undetermined;
|
||||
QVariant m_data;
|
||||
|
||||
friend class QCoreApplication;
|
||||
};
|
||||
|
||||
#define QT_PERMISSION(ClassName) \
|
||||
Q_GADGET \
|
||||
using QtPermissionHelper = void; \
|
||||
friend class QPermission; \
|
||||
public: \
|
||||
ClassName(); \
|
||||
ClassName(const ClassName &other) noexcept; \
|
||||
ClassName(ClassName &&other) noexcept; \
|
||||
~ClassName() noexcept; \
|
||||
ClassName &operator=(const ClassName &other) noexcept; \
|
||||
QT_MOVE_ASSIGNMENT_OPERATOR_IMPL_VIA_PURE_SWAP(ClassName) \
|
||||
void swap(ClassName &other) noexcept { d.swap(other.d); } \
|
||||
private: \
|
||||
QtPrivate::QExplicitlySharedDataPointerV2<ClassName##Private> d;
|
||||
|
||||
class QLocationPermissionPrivate;
|
||||
class Q_CORE_EXPORT QLocationPermission
|
||||
{
|
||||
QT_PERMISSION(QLocationPermission)
|
||||
public:
|
||||
enum Accuracy { Approximate, Precise };
|
||||
Q_ENUM(Accuracy)
|
||||
|
||||
void setAccuracy(Accuracy accuracy);
|
||||
Accuracy accuracy() const;
|
||||
|
||||
enum Availability { WhenInUse, Always };
|
||||
Q_ENUM(Availability)
|
||||
|
||||
void setAvailability(Availability availability);
|
||||
Availability availability() const;
|
||||
};
|
||||
Q_DECLARE_SHARED(QLocationPermission);
|
||||
|
||||
class QCalendarPermissionPrivate;
|
||||
class Q_CORE_EXPORT QCalendarPermission
|
||||
{
|
||||
QT_PERMISSION(QCalendarPermission)
|
||||
public:
|
||||
void setReadOnly(bool enable);
|
||||
bool isReadOnly() const;
|
||||
};
|
||||
Q_DECLARE_SHARED(QCalendarPermission);
|
||||
|
||||
class QContactsPermissionPrivate;
|
||||
class Q_CORE_EXPORT QContactsPermission
|
||||
{
|
||||
QT_PERMISSION(QContactsPermission)
|
||||
public:
|
||||
void setReadOnly(bool enable);
|
||||
bool isReadOnly() const;
|
||||
};
|
||||
Q_DECLARE_SHARED(QContactsPermission);
|
||||
|
||||
#define Q_DECLARE_MINIMAL_PERMISSION(ClassName) \
|
||||
class ClassName##Private; \
|
||||
class Q_CORE_EXPORT ClassName \
|
||||
{ \
|
||||
QT_PERMISSION(ClassName) \
|
||||
}; \
|
||||
Q_DECLARE_SHARED(ClassName);
|
||||
|
||||
Q_DECLARE_MINIMAL_PERMISSION(QCameraPermission);
|
||||
Q_DECLARE_MINIMAL_PERMISSION(QMicrophonePermission);
|
||||
Q_DECLARE_MINIMAL_PERMISSION(QBluetoothPermission);
|
||||
|
||||
#undef QT_PERMISSION
|
||||
#undef Q_DECLARE_MINIMAL_PERMISSION
|
||||
|
||||
QT_END_NAMESPACE
|
||||
|
||||
#endif // QPERMISSIONS_H
|
41
src/corelib/kernel/qpermissions_p.h
Normal file
41
src/corelib/kernel/qpermissions_p.h
Normal file
@ -0,0 +1,41 @@
|
||||
// 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 QPERMISSIONS_P_H
|
||||
#define QPERMISSIONS_P_H
|
||||
|
||||
#include "qpermissions.h"
|
||||
|
||||
#include <private/qglobal_p.h>
|
||||
#include <QtCore/qloggingcategory.h>
|
||||
|
||||
#include <functional>
|
||||
|
||||
QT_REQUIRE_CONFIG(permissions);
|
||||
|
||||
//
|
||||
// W A R N I N G
|
||||
// -------------
|
||||
//
|
||||
// This file is not part of the Qt API. It exists purely as an
|
||||
// implementation detail. This header file may change from version to
|
||||
// version without notice, or even be removed.
|
||||
//
|
||||
// We mean it.
|
||||
//
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
|
||||
Q_DECLARE_LOGGING_CATEGORY(lcPermissions)
|
||||
|
||||
namespace QPermissions::Private
|
||||
{
|
||||
using PermissionCallback = std::function<void(Qt::PermissionStatus)>;
|
||||
|
||||
Qt::PermissionStatus checkPermission(const QPermission &permission);
|
||||
void requestPermission(const QPermission &permission, const PermissionCallback &callback);
|
||||
}
|
||||
|
||||
QT_END_NAMESPACE
|
||||
|
||||
#endif // QPERMISSIONS_P_H
|
1
tests/manual/permissions/.gitignore
vendored
Normal file
1
tests/manual/permissions/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
tst_qpermissions
|
7
tests/manual/permissions/CMakeLists.txt
Normal file
7
tests/manual/permissions/CMakeLists.txt
Normal file
@ -0,0 +1,7 @@
|
||||
|
||||
qt_internal_add_test(tst_qpermissions
|
||||
SOURCES
|
||||
tst_qpermissions.cpp
|
||||
LIBRARIES
|
||||
Qt::CorePrivate
|
||||
)
|
111
tests/manual/permissions/tst_qpermissions.cpp
Normal file
111
tests/manual/permissions/tst_qpermissions.cpp
Normal file
@ -0,0 +1,111 @@
|
||||
// Copyright (C) 2022 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
||||
|
||||
#include <QTest>
|
||||
|
||||
#include <QtCore/qpermissions.h>
|
||||
#include <QtCore/qthread.h>
|
||||
#include <QtCore/qmutex.h>
|
||||
#include <QtCore/qwaitcondition.h>
|
||||
#include <QtCore/qtimer.h>
|
||||
|
||||
class tst_QPermissions : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
private slots:
|
||||
void initTestCase_data();
|
||||
|
||||
void checkPermission();
|
||||
void checkPermissionInNonMainThread();
|
||||
|
||||
void requestPermission();
|
||||
void requestPermissionInNonMainThread();
|
||||
};
|
||||
|
||||
void tst_QPermissions::initTestCase_data()
|
||||
{
|
||||
QTest::addColumn<QPermission>("permission");
|
||||
|
||||
QTest::newRow("Camera") << QPermission(QCameraPermission{});
|
||||
QTest::newRow("Microphone") << QPermission(QMicrophonePermission{});
|
||||
QTest::newRow("Bluetooth") << QPermission(QBluetoothPermission{});
|
||||
QTest::newRow("Contacts") << QPermission(QContactsPermission{});
|
||||
QTest::newRow("Calendar") << QPermission(QCalendarPermission{});
|
||||
QTest::newRow("Location") << QPermission(QLocationPermission{});
|
||||
}
|
||||
|
||||
void tst_QPermissions::checkPermission()
|
||||
{
|
||||
QFETCH_GLOBAL(QPermission, permission);
|
||||
qApp->checkPermission(permission);
|
||||
}
|
||||
|
||||
class Thread : public QThread
|
||||
{
|
||||
public:
|
||||
QMutex mutex;
|
||||
QWaitCondition cond;
|
||||
std::function<void()> function;
|
||||
|
||||
void run() override
|
||||
{
|
||||
QMutexLocker locker(&mutex);
|
||||
function();
|
||||
cond.wakeOne();
|
||||
}
|
||||
};
|
||||
|
||||
void tst_QPermissions::checkPermissionInNonMainThread()
|
||||
{
|
||||
QFETCH_GLOBAL(QPermission, permission);
|
||||
|
||||
Thread thread;
|
||||
thread.function = [=]{
|
||||
qApp->checkPermission(permission);
|
||||
};
|
||||
|
||||
QVERIFY(!thread.isFinished());
|
||||
QMutexLocker locker(&thread.mutex);
|
||||
thread.start();
|
||||
QVERIFY(!thread.isFinished());
|
||||
thread.cond.wait(locker.mutex());
|
||||
QVERIFY(thread.wait(1000));
|
||||
QVERIFY(thread.isFinished());
|
||||
}
|
||||
|
||||
void tst_QPermissions::requestPermission()
|
||||
{
|
||||
QFETCH_GLOBAL(QPermission, permission);
|
||||
QTimer::singleShot(0, [=] {
|
||||
qApp->requestPermission(permission, [=](auto result) {
|
||||
qDebug() << result;
|
||||
Q_ASSERT(QThread::currentThread() == thread());
|
||||
qApp->exit();
|
||||
});
|
||||
});
|
||||
qApp->exec();
|
||||
}
|
||||
|
||||
void tst_QPermissions::requestPermissionInNonMainThread()
|
||||
{
|
||||
QFETCH_GLOBAL(QPermission, permission);
|
||||
|
||||
QTest::ignoreMessage(QtWarningMsg, "Permissions can only be requested from the GUI (main) thread");
|
||||
|
||||
Thread thread;
|
||||
thread.function = [&]{
|
||||
qApp->requestPermission(permission, [&]() {});
|
||||
};
|
||||
|
||||
QVERIFY(!thread.isFinished());
|
||||
QMutexLocker locker(&thread.mutex);
|
||||
thread.start();
|
||||
QVERIFY(!thread.isFinished());
|
||||
thread.cond.wait(locker.mutex());
|
||||
QVERIFY(thread.wait(1000));
|
||||
QVERIFY(thread.isFinished());
|
||||
}
|
||||
|
||||
QTEST_MAIN(tst_QPermissions)
|
||||
#include "tst_qpermissions.moc"
|
Loading…
Reference in New Issue
Block a user