summaryrefslogtreecommitdiffstats
path: root/src/corelib/kernel
diff options
context:
space:
mode:
authorTor Arne Vestbø <tor.arne.vestbo@qt.io>2022-05-06 17:00:51 +0200
committerAssam Boudjelthia <assam.boudjelthia@qt.io>2022-11-09 14:22:29 +0200
commitef935f6e37a24f52255e6696b85a0fa9aaa7361a (patch)
treeeed0f269293c59fa9c7fba8f3aba53871e798729 /src/corelib/kernel
parent64dc886db7af91813a19313215cb3ef6031d0cb2 (diff)
Plumb public permission APIs to Android backend
The lock and unlock of the Android deadlock mutex is now part of the internal implementation instead of limited to the enum based permission API. It is unclear why 8bca441b6f65 added the guard only to this API and not to the string based API as well. The check for isBackgroundLocationApi29 has been removed, as the logic seemingly resulted in accepting every single permission type except location permissions if used via the enum-based API. Since Android's platform permission API doesn't have an Undetermined status, we keep a hash of the status for each permission type, and by default checkPermission() would return Undetermined, until a requestPermission() call is done which updates the internal hash, and after that checkPermission() would return properly Granted/Denied. Task-number: QTBUG-100413 Change-Id: Ia95c76af754481a281bc90198e349966c9c2da52 Reviewed-by: Tor Arne Vestbø <tor.arne.vestbo@qt.io>
Diffstat (limited to 'src/corelib/kernel')
-rw-r--r--src/corelib/kernel/qpermissions.cpp33
-rw-r--r--src/corelib/kernel/qpermissions_android.cpp137
2 files changed, 170 insertions, 0 deletions
diff --git a/src/corelib/kernel/qpermissions.cpp b/src/corelib/kernel/qpermissions.cpp
index be9717694a..4fded99915 100644
--- a/src/corelib/kernel/qpermissions.cpp
+++ b/src/corelib/kernel/qpermissions.cpp
@@ -280,6 +280,10 @@ QMetaType QPermission::type() const
\li Apple
\li \l{apple-usage-description}{Usage description}
\li \c NSCameraUsageDescription
+ \row
+ \li Android
+ \li \l{android-uses-permission}{\c{uses-permission}}
+ \li \c android.permission.CAMERA
\include permissions.qdocinc end-usage-declarations
\include permissions.qdocinc permission-metadata
@@ -298,6 +302,10 @@ QT_DEFINE_PERMISSION_SPECIAL_FUNCTIONS(QCameraPermission)
\li Apple
\li \l{apple-usage-description}{Usage description}
\li \c NSMicrophoneUsageDescription
+ \row
+ \li Android
+ \li \l{android-uses-permission}{\c{uses-permission}}
+ \li \c android.permission.RECORD_AUDIO
\include permissions.qdocinc end-usage-declarations
\include permissions.qdocinc permission-metadata
@@ -316,6 +324,10 @@ QT_DEFINE_PERMISSION_SPECIAL_FUNCTIONS(QMicrophonePermission)
\li Apple
\li \l{apple-usage-description}{Usage description}
\li \c NSBluetoothAlwaysUsageDescription
+ \row
+ \li Android
+ \li \l{android-uses-permission}{\c{uses-permission}}
+ \li \c android.permission.BLUETOOTH
\include permissions.qdocinc end-usage-declarations
\include permissions.qdocinc permission-metadata
@@ -341,6 +353,17 @@ QT_DEFINE_PERMISSION_SPECIAL_FUNCTIONS(QBluetoothPermission)
\li \c NSLocationWhenInUseUsageDescription, and
\c NSLocationAlwaysUsageDescription if requesting
QLocationPermission::Always
+ \row
+ \li Android
+ \li \l{android-uses-permission}{\c{uses-permission}}
+ \li \list
+ \li \c android.permission.ACCESS_FINE_LOCATION for QLocationPermission::Precise
+ \li \c android.permission.ACCESS_COARSE_LOCATION for QLocationPermission::Approximate
+ \li \c android.permission.ACCESS_BACKGROUND_LOCATION for QLocationPermission::Always
+ \endlist
+ \note QLocationPermission::Always \c uses-permission string has
+ to be combined with one or both of QLocationPermission::Precise
+ and QLocationPermission::Approximate strings.
\include permissions.qdocinc end-usage-declarations
\include permissions.qdocinc permission-metadata
@@ -425,6 +448,11 @@ QLocationPermission::Availability QLocationPermission::availability() const
\li Apple
\li \l{apple-usage-description}{Usage description}
\li \c NSContactsUsageDescription
+ \row
+ \li Android
+ \li \l{android-uses-permission}{\c{uses-permission}}
+ \li \c android.permission.READ_CONTACTS. \c android.permission.WRITE_CONTACTS if
+ QContactsPermission::isReadOnly() is set to \c false.
\include permissions.qdocinc end-usage-declarations
\include permissions.qdocinc permission-metadata
@@ -468,6 +496,11 @@ bool QContactsPermission::isReadOnly() const
\li Apple
\li \l{apple-usage-description}{Usage description}
\li \c NSCalendarsUsageDescription
+ \row
+ \li Android
+ \li \l{android-uses-permission}{\c{uses-permission}}
+ \li \c android.permission.READ_CALENDAR. \c android.permission.WRITE_CALENDAR if
+ QContactsPermission::isReadOnly() is set to \c false.
\include permissions.qdocinc end-usage-declarations
\include permissions.qdocinc permission-metadata
diff --git a/src/corelib/kernel/qpermissions_android.cpp b/src/corelib/kernel/qpermissions_android.cpp
new file mode 100644
index 0000000000..cdafb0144c
--- /dev/null
+++ b/src/corelib/kernel/qpermissions_android.cpp
@@ -0,0 +1,137 @@
+// 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 <QtCore/qstringlist.h>
+#include <QtCore/qfuture.h>
+#include <QtCore/qhash.h>
+
+#include "private/qandroidextras_p.h"
+
+using namespace Qt::StringLiterals;
+
+QT_BEGIN_NAMESPACE
+
+static QStringList nativeLocationPermission(const QLocationPermission &permission)
+{
+ QStringList nativeLocationPermissionList;
+ const int sdkVersion = QtAndroidPrivate::androidSdkVersion();
+ static QString backgroundLocation = u"android.permission.ACCESS_BACKGROUND_LOCATION"_s;
+ static QString fineLocation = u"android.permission.ACCESS_FINE_LOCATION"_s;
+ static QString coarseLocation = u"android.permission.ACCESS_COARSE_LOCATION"_s;
+
+ // Since Android API 30, background location cannot be requested along
+ // with fine or coarse location, but it should be requested separately after
+ // the latter have been granted, see
+ // https://developer.android.com/training/location/permissions
+ if (sdkVersion < 30 || permission.availability() == QLocationPermission::WhenInUse) {
+ if (permission.accuracy() == QLocationPermission::Approximate) {
+ nativeLocationPermissionList << coarseLocation;
+ } else {
+ nativeLocationPermissionList << fineLocation;
+ // Since Android API 31, if precise location is requested, it's advised
+ // to request both fine and coarse location permissions, see
+ // https://developer.android.com/training/location/permissions#approximate-request
+ if (sdkVersion >= 31)
+ nativeLocationPermissionList << coarseLocation;
+ }
+ }
+
+ // NOTE: before Android API 29, background permission doesn't exist yet.
+
+ // Keep the background permission in front to be able to use first()
+ // on the list in checkPermission() because it takes single permission.
+ if (sdkVersion >= 29 && permission.availability() == QLocationPermission::Always)
+ nativeLocationPermissionList.prepend(backgroundLocation);
+
+ return nativeLocationPermissionList;
+}
+
+static QStringList nativeStringsFromPermission(const QPermission &permission)
+{
+ const auto id = permission.type().id();
+ if (id == qMetaTypeId<QLocationPermission>()) {
+ return nativeLocationPermission(permission.data<QLocationPermission>());
+ } else if (id == qMetaTypeId<QCameraPermission>()) {
+ return { u"android.permission.CAMERA"_s };
+ } else if (id == qMetaTypeId<QMicrophonePermission>()) {
+ return { u"android.permission.RECORD_AUDIO"_s };
+ } else if (id == qMetaTypeId<QBluetoothPermission>()) {
+ // TODO: handle Android 12 new bluetooth permissions
+ return { u"android.permission.BLUETOOTH"_s };
+ } else if (id == qMetaTypeId<QContactsPermission>()) {
+ const auto readContactsString = u"android.permission.READ_CONTACTS"_s;
+ if (permission.data<QContactsPermission>().isReadOnly())
+ return { readContactsString };
+ return { readContactsString, u"android.permission.WRITE_CONTACTS"_s };
+ } else if (id == qMetaTypeId<QCalendarPermission>()) {
+ const auto readContactsString = u"android.permission.READ_CALENDAR"_s;
+ if (permission.data<QCalendarPermission>().isReadOnly())
+ return { readContactsString };
+ return { readContactsString, u"android.permission.WRITE_CALENDAR"_s };
+ }
+
+ return {};
+}
+
+static Qt::PermissionStatus
+permissionStatusForAndroidResult(QtAndroidPrivate::PermissionResult result)
+{
+ switch (result) {
+ case QtAndroidPrivate::PermissionResult::Authorized: return Qt::PermissionStatus::Granted;
+ case QtAndroidPrivate::PermissionResult::Denied: return Qt::PermissionStatus::Denied;
+ default: return Qt::PermissionStatus::Undetermined;
+ }
+}
+
+using PermissionStatusHash = QHash<int, Qt::PermissionStatus>;
+Q_GLOBAL_STATIC_WITH_ARGS(PermissionStatusHash, g_permissionStatusHash, ({
+ { qMetaTypeId<QCameraPermission>(), Qt::PermissionStatus::Undetermined },
+ { qMetaTypeId<QMicrophonePermission>(), Qt::PermissionStatus::Undetermined },
+ { qMetaTypeId<QBluetoothPermission>(), Qt::PermissionStatus::Undetermined },
+ { qMetaTypeId<QContactsPermission>(), Qt::PermissionStatus::Undetermined },
+ { qMetaTypeId<QCalendarPermission>(), Qt::PermissionStatus::Undetermined },
+ { qMetaTypeId<QLocationPermission>(), Qt::PermissionStatus::Undetermined }
+}));
+
+namespace QPermissions::Private
+{
+ Qt::PermissionStatus checkPermission(const QPermission &permission)
+ {
+ const auto nativePermissionList = nativeStringsFromPermission(permission);
+ if (nativePermissionList.isEmpty())
+ return Qt::PermissionStatus::Granted;
+
+ const auto result = QtAndroidPrivate::checkPermission(nativePermissionList.first()).result();
+ const auto status = permissionStatusForAndroidResult(result);
+ const auto it = g_permissionStatusHash->constFind(permission.type().id());
+ const bool foundStatus = (it != g_permissionStatusHash->constEnd());
+ const bool itUndetermined = foundStatus && (*it) == Qt::PermissionStatus::Undetermined;
+ if (status == Qt::PermissionStatus::Denied && itUndetermined)
+ return Qt::PermissionStatus::Undetermined;
+ return status;
+ }
+
+ void requestPermission(const QPermission &permission,
+ const QPermissions::Private::PermissionCallback &callback)
+ {
+ const auto nativePermissionList = nativeStringsFromPermission(permission);
+ if (nativePermissionList.isEmpty()) {
+ callback(Qt::PermissionStatus::Granted);
+ return;
+ }
+
+ QtAndroidPrivate::requestPermissions(nativePermissionList).then(qApp,
+ [callback, permission](QFuture<QtAndroidPrivate::PermissionResult> future) {
+ const auto result = future.isValid() ? future.result() : QtAndroidPrivate::Denied;
+ const auto status = permissionStatusForAndroidResult(result);
+ g_permissionStatusHash->insert(permission.type().id(), status);
+ callback(status);
+ }
+ );
+ }
+}
+
+QT_END_NAMESPACE