Revision control
Copy as Markdown
// 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
// Qt-Security score:significant reason:default
#include "qpermissions.h"
#include "qpermissions_p.h"
#include <QtCore/qstringlist.h>
#include <QtCore/qfuture.h>
#include <QtCore/qhash.h>
#include "private/qandroidextras_p.h"
QT_BEGIN_NAMESPACE
using namespace Qt::StringLiterals;
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
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
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 nativeBluetoothPermission(const QBluetoothPermission &permission)
{
// for the details.
// API Level < 31
static QString bluetoothGeneral = u"android.permission.BLUETOOTH"_s;
static QString fineLocation = u"android.permission.ACCESS_FINE_LOCATION"_s;
// API Level >= 31
static QString bluetoothScan = u"android.permission.BLUETOOTH_SCAN"_s;
static QString bluetoothAdvertise = u"android.permission.BLUETOOTH_ADVERTISE"_s;
static QString bluetoothConnect = u"android.permission.BLUETOOTH_CONNECT"_s;
if (QtAndroidPrivate::androidSdkVersion() < 31) {
return {bluetoothGeneral, fineLocation};
} else {
const auto modes = permission.communicationModes();
QStringList permissionList;
if (modes & QBluetoothPermission::Advertise)
permissionList << bluetoothAdvertise;
if (modes & QBluetoothPermission::Access)
permissionList << bluetoothScan << bluetoothConnect;
return permissionList;
}
}
static QStringList nativeStringsFromPermission(const QPermission &permission)
{
const auto id = permission.type().id();
if (id == qMetaTypeId<QLocationPermission>()) {
return nativeLocationPermission(*permission.value<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>()) {
return nativeBluetoothPermission(*permission.value<QBluetoothPermission>());
} else if (id == qMetaTypeId<QContactsPermission>()) {
const auto readContactsString = u"android.permission.READ_CONTACTS"_s;
switch (permission.value<QContactsPermission>()->accessMode()) {
case QContactsPermission::AccessMode::ReadOnly:
return { readContactsString };
case QContactsPermission::AccessMode::ReadWrite:
return { readContactsString, u"android.permission.WRITE_CONTACTS"_s };
}
Q_UNREACHABLE_RETURN({});
} else if (id == qMetaTypeId<QCalendarPermission>()) {
const auto readCalendarString = u"android.permission.READ_CALENDAR"_s;
const auto writeCalendarString = u"android.permission.WRITE_CALENDAR"_s;
switch (permission.value<QCalendarPermission>()->accessMode()) {
case QCalendarPermission::AccessMode::ReadOnly:
return { readCalendarString };
case QCalendarPermission::AccessMode::ReadWrite:
return { readCalendarString, writeCalendarString };
case QCalendarPermission::AccessMode::WriteOnly:
return { writeCalendarString };
}
Q_UNREACHABLE_RETURN({});
}
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 }
}));
static Qt::PermissionStatus
getCombinedStatus(const QList<QtAndroidPrivate::PermissionResult> &androidResults)
{
// Android returns only Denied or Granted
for (const auto &result : androidResults) {
const auto status = permissionStatusForAndroidResult(result);
if (status == Qt::PermissionStatus::Denied)
return status;
}
return Qt::PermissionStatus::Granted;
}
namespace QPermissions::Private
{
Qt::PermissionStatus checkPermission(const QPermission &permission)
{
const auto nativePermissionList = nativeStringsFromPermission(permission);
if (nativePermissionList.isEmpty())
return Qt::PermissionStatus::Granted;
QList<QtAndroidPrivate::PermissionResult> androidResults;
androidResults.reserve(nativePermissionList.size());
for (const auto &nativePermission : nativePermissionList)
androidResults.push_back(QtAndroidPrivate::checkPermission(nativePermission).result());
const auto status = getCombinedStatus(androidResults);
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 androidResults = future.isValid() ? future.results()
: QList{QtAndroidPrivate::Denied};
const auto status = getCombinedStatus(androidResults);
g_permissionStatusHash->insert(permission.type().id(), status);
callback(status);
}
);
}
}
QT_END_NAMESPACE