From 0198a74b72dfda9f40b2b786ce1d2ccb59780cbc Mon Sep 17 00:00:00 2001 From: Ivan Solovev Date: Mon, 3 Apr 2023 13:00:59 +0200 Subject: [PATCH] QBluetoothPermission: introduce fine-grained permissions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This commit introduces fine-grained Bluetooth permissions control to the QBluetoothPermission class. For now the fine-tuning of the permissions is only supported on Android. On Apple enabling any of the permissions is equivalent to requesting full Bluetooth control. Task-number: QTBUG-109964 Change-Id: Ie7ac6577cf6a21419b73b33f8cf7e87bc3f8cf43 Reviewed-by: Marc Mutz Reviewed-by: Tor Arne Vestbø --- src/corelib/kernel/qpermissions.cpp | 53 ++++++++++++++++++++- src/corelib/kernel/qpermissions.h | 27 ++++++++++- src/corelib/kernel/qpermissions_android.cpp | 17 +++++-- 3 files changed, 90 insertions(+), 7 deletions(-) diff --git a/src/corelib/kernel/qpermissions.cpp b/src/corelib/kernel/qpermissions.cpp index b3046f0a446..0d15901948b 100644 --- a/src/corelib/kernel/qpermissions.cpp +++ b/src/corelib/kernel/qpermissions.cpp @@ -387,9 +387,60 @@ QT_PERMISSION_IMPL_COMMON(QMicrophonePermission) */ QT_PERMISSION_IMPL_COMMON(QBluetoothPermission) - : u{} // stateless, atm + : u{ShortData{CommunicationMode::Default, {}}} {} +/*! + \enum QBluetoothPermission::CommunicationMode + \since 6.6 + + This enum is used to control the allowed Bluetooth communication modes. + + \value Access Allow this device to access other Bluetooth devices. This + includes scanning for nearby devices and connecting to them. + \value Advertise Allow other Bluetooth devices to discover this device. + \value Default This configuration is used by default. + + \note The fine-grained permissions are currently supported only on + Android 12 and newer. On older Android versions, as well as on Apple + operating systems, any mode results in full Bluetooth access. + + \note For now the \c Access mode on Android also requests the + \l {QLocationPermission::Precise}{precise location} permission. + This permission coupling may change in the future. +*/ + +/*! + \since 6.6 + + Sets the allowed Bluetooth communication modes to \a modes. + + \note A default-constructed instance of \l {QBluetoothPermission::} + {CommunicationModes} has no sense, so an attempt to set such a mode will + raise a \c {qWarning()} and fall back to using the + \l {QBluetoothPermission::}{Default} mode. +*/ +void QBluetoothPermission::setCommunicationModes(CommunicationModes modes) +{ + if (modes == CommunicationModes{}) { + qCWarning(lcPermissions, "QBluetoothPermission: trying to set an invalid empty mode. " + "Falling back to CommunicationMode::Default."); + u.data.mode = Default; + } else { + u.data.mode = static_cast(modes.toInt()); + } +} + +/*! + \since 6.6 + + Returns the allowed Bluetooth communication modes. +*/ +QBluetoothPermission::CommunicationModes QBluetoothPermission::communicationModes() const +{ + return u.data.mode; +} + /*! \class QLocationPermission \brief Access the user's location. diff --git a/src/corelib/kernel/qpermissions.h b/src/corelib/kernel/qpermissions.h index 3bd1fda461e..dee606674f1 100644 --- a/src/corelib/kernel/qpermissions.h +++ b/src/corelib/kernel/qpermissions.h @@ -173,6 +173,32 @@ private: }; Q_DECLARE_SHARED(QContactsPermission) +class QBluetoothPermissionPrivate; +class QBluetoothPermission +{ + Q_GADGET_EXPORT(Q_CORE_EXPORT) +public: + enum CommunicationMode : quint8 { + Access = 0x01, + Advertise = 0x02, + Default = Access | Advertise, + }; + Q_DECLARE_FLAGS(CommunicationModes, CommunicationMode) + Q_FLAG(CommunicationModes) + + Q_CORE_EXPORT void setCommunicationModes(CommunicationModes modes); + Q_CORE_EXPORT CommunicationModes communicationModes() const; + +private: + struct ShortData { + CommunicationMode mode; + char reserved[sizeof(void*) - sizeof(mode)]; + }; + QT_PERMISSION(QBluetoothPermission) +}; +Q_DECLARE_OPERATORS_FOR_FLAGS(QBluetoothPermission::CommunicationModes) +Q_DECLARE_SHARED(QBluetoothPermission) + #define Q_DECLARE_MINIMAL_PERMISSION(ClassName) \ class ClassName##Private; \ class ClassName \ @@ -184,7 +210,6 @@ Q_DECLARE_SHARED(QContactsPermission) Q_DECLARE_MINIMAL_PERMISSION(QCameraPermission) Q_DECLARE_MINIMAL_PERMISSION(QMicrophonePermission) -Q_DECLARE_MINIMAL_PERMISSION(QBluetoothPermission) #undef QT_PERMISSION #undef Q_DECLARE_MINIMAL_PERMISSION diff --git a/src/corelib/kernel/qpermissions_android.cpp b/src/corelib/kernel/qpermissions_android.cpp index cda4c3f896c..6c21ded72cd 100644 --- a/src/corelib/kernel/qpermissions_android.cpp +++ b/src/corelib/kernel/qpermissions_android.cpp @@ -49,7 +49,7 @@ static QStringList nativeLocationPermission(const QLocationPermission &permissio return nativeLocationPermissionList; } -static QStringList nativeBluetoothPermission() +static QStringList nativeBluetoothPermission(const QBluetoothPermission &permission) { // See https://developer.android.com/guide/topics/connectivity/bluetooth/permissions // for the details. @@ -64,10 +64,17 @@ static QStringList nativeBluetoothPermission() // strictly necessary for API Level >= 31. See QTBUG-112164. static QString fineLocation = u"android.permission.ACCESS_FINE_LOCATION"_s; - if (QtAndroidPrivate::androidSdkVersion() < 31) + if (QtAndroidPrivate::androidSdkVersion() < 31) { return {bluetoothGeneral, fineLocation}; - else - return {bluetoothScan, bluetoothAdvertise, bluetoothConnect, fineLocation}; + } else { + const auto modes = permission.communicationModes(); + QStringList permissionList; + if (modes & QBluetoothPermission::Advertise) + permissionList << bluetoothAdvertise; + if (modes & QBluetoothPermission::Access) + permissionList << bluetoothScan << bluetoothConnect << fineLocation; + return permissionList; + } } static QStringList nativeStringsFromPermission(const QPermission &permission) @@ -80,7 +87,7 @@ static QStringList nativeStringsFromPermission(const QPermission &permission) } else if (id == qMetaTypeId()) { return { u"android.permission.RECORD_AUDIO"_s }; } else if (id == qMetaTypeId()) { - return nativeBluetoothPermission(); + return nativeBluetoothPermission(*permission.value()); } else if (id == qMetaTypeId()) { const auto readContactsString = u"android.permission.READ_CONTACTS"_s; switch (permission.value()->accessMode()) {