Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qpermissions_android.cpp
Go to the documentation of this file.
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// Qt-Security score:significant reason:default
4
5#include "qpermissions.h"
7
8#include <QtCore/qstringlist.h>
9#include <QtCore/qfuture.h>
10#include <QtCore/qhash.h>
11
12#include "private/qandroidextras_p.h"
13
15
16using namespace Qt::StringLiterals;
17
19{
20 QStringList nativeLocationPermissionList;
21 const int sdkVersion = QtAndroidPrivate::androidSdkVersion();
22 static QString backgroundLocation = u"android.permission.ACCESS_BACKGROUND_LOCATION"_s;
23 static QString fineLocation = u"android.permission.ACCESS_FINE_LOCATION"_s;
24 static QString coarseLocation = u"android.permission.ACCESS_COARSE_LOCATION"_s;
25
26 // Since Android API 30, background location cannot be requested along
27 // with fine or coarse location, but it should be requested separately after
28 // the latter have been granted, see
29 // https://developer.android.com/training/location/permissions
30 if (sdkVersion < 30 || permission.availability() == QLocationPermission::WhenInUse) {
31 if (permission.accuracy() == QLocationPermission::Approximate) {
32 nativeLocationPermissionList << coarseLocation;
33 } else {
34 nativeLocationPermissionList << fineLocation;
35 // Since Android API 31, if precise location is requested, it's advised
36 // to request both fine and coarse location permissions, see
37 // https://developer.android.com/training/location/permissions#approximate-request
38 if (sdkVersion >= 31)
39 nativeLocationPermissionList << coarseLocation;
40 }
41 }
42
43 // NOTE: before Android API 29, background permission doesn't exist yet.
44
45 // Keep the background permission in front to be able to use first()
46 // on the list in checkPermission() because it takes single permission.
47 if (sdkVersion >= 29 && permission.availability() == QLocationPermission::Always)
48 nativeLocationPermissionList.prepend(backgroundLocation);
49
50 return nativeLocationPermissionList;
51}
52
54{
55 // See https://developer.android.com/guide/topics/connectivity/bluetooth/permissions
56 // for the details.
57
58 // API Level < 31
59 static QString bluetoothGeneral = u"android.permission.BLUETOOTH"_s;
60 static QString fineLocation = u"android.permission.ACCESS_FINE_LOCATION"_s;
61 // API Level >= 31
62 static QString bluetoothScan = u"android.permission.BLUETOOTH_SCAN"_s;
63 static QString bluetoothAdvertise = u"android.permission.BLUETOOTH_ADVERTISE"_s;
64 static QString bluetoothConnect = u"android.permission.BLUETOOTH_CONNECT"_s;
65
66 if (QtAndroidPrivate::androidSdkVersion() < 31) {
67 return {bluetoothGeneral, fineLocation};
68 } else {
69 const auto modes = permission.communicationModes();
70 QStringList permissionList;
71 if (modes & QBluetoothPermission::Advertise)
72 permissionList << bluetoothAdvertise;
73 if (modes & QBluetoothPermission::Access)
74 permissionList << bluetoothScan << bluetoothConnect;
75 return permissionList;
76 }
77}
78
80{
81 const auto id = permission.type().id();
82 if (id == qMetaTypeId<QLocationPermission>()) {
83 return nativeLocationPermission(*permission.value<QLocationPermission>());
84 } else if (id == qMetaTypeId<QCameraPermission>()) {
85 return { u"android.permission.CAMERA"_s };
86 } else if (id == qMetaTypeId<QMicrophonePermission>()) {
87 return { u"android.permission.RECORD_AUDIO"_s };
88 } else if (id == qMetaTypeId<QBluetoothPermission>()) {
89 return nativeBluetoothPermission(*permission.value<QBluetoothPermission>());
90 } else if (id == qMetaTypeId<QContactsPermission>()) {
91 const auto readContactsString = u"android.permission.READ_CONTACTS"_s;
92 switch (permission.value<QContactsPermission>()->accessMode()) {
93 case QContactsPermission::AccessMode::ReadOnly:
94 return { readContactsString };
95 case QContactsPermission::AccessMode::ReadWrite:
96 return { readContactsString, u"android.permission.WRITE_CONTACTS"_s };
97 }
98 Q_UNREACHABLE_RETURN({});
99 } else if (id == qMetaTypeId<QCalendarPermission>()) {
100 const auto readCalendarString = u"android.permission.READ_CALENDAR"_s;
101 const auto writeCalendarString = u"android.permission.WRITE_CALENDAR"_s;
102 switch (permission.value<QCalendarPermission>()->accessMode()) {
103 case QCalendarPermission::AccessMode::ReadOnly:
104 return { readCalendarString };
105 case QCalendarPermission::AccessMode::ReadWrite:
106 return { readCalendarString, writeCalendarString };
107 case QCalendarPermission::AccessMode::WriteOnly:
108 return { writeCalendarString };
109 }
110 Q_UNREACHABLE_RETURN({});
111 }
112
113 return {};
114}
115
116static Qt::PermissionStatus
117permissionStatusForAndroidResult(QtAndroidPrivate::PermissionResult result)
118{
119 switch (result) {
120 case QtAndroidPrivate::PermissionResult::Authorized: return Qt::PermissionStatus::Granted;
121 case QtAndroidPrivate::PermissionResult::Denied: return Qt::PermissionStatus::Denied;
122 default: return Qt::PermissionStatus::Undetermined;
123 }
124}
125
126using PermissionStatusHash = QHash<int, Qt::PermissionStatus>;
127Q_GLOBAL_STATIC_WITH_ARGS(PermissionStatusHash, g_permissionStatusHash, ({
128 { qMetaTypeId<QCameraPermission>(), Qt::PermissionStatus::Undetermined },
129 { qMetaTypeId<QMicrophonePermission>(), Qt::PermissionStatus::Undetermined },
130 { qMetaTypeId<QBluetoothPermission>(), Qt::PermissionStatus::Undetermined },
131 { qMetaTypeId<QContactsPermission>(), Qt::PermissionStatus::Undetermined },
132 { qMetaTypeId<QCalendarPermission>(), Qt::PermissionStatus::Undetermined },
133 { qMetaTypeId<QLocationPermission>(), Qt::PermissionStatus::Undetermined }
134}));
135
136static Qt::PermissionStatus
137getCombinedStatus(const QList<QtAndroidPrivate::PermissionResult> &androidResults)
138{
139 // Android returns only Denied or Granted
140 for (const auto &result : androidResults) {
141 const auto status = permissionStatusForAndroidResult(result);
142 if (status == Qt::PermissionStatus::Denied)
143 return status;
144 }
145 return Qt::PermissionStatus::Granted;
146}
147
148namespace QPermissions::Private
149{
151 {
152 const auto nativePermissionList = nativeStringsFromPermission(permission);
153 if (nativePermissionList.isEmpty())
154 return Qt::PermissionStatus::Granted;
155
156 QList<QtAndroidPrivate::PermissionResult> androidResults;
157 androidResults.reserve(nativePermissionList.size());
158 for (const auto &nativePermission : nativePermissionList)
159 androidResults.push_back(QtAndroidPrivate::checkPermission(nativePermission).result());
160
161 const auto status = getCombinedStatus(androidResults);
162 const auto it = g_permissionStatusHash->constFind(permission.type().id());
163 const bool foundStatus = (it != g_permissionStatusHash->constEnd());
164 const bool itUndetermined = foundStatus && (*it) == Qt::PermissionStatus::Undetermined;
165 if (status == Qt::PermissionStatus::Denied && itUndetermined)
166 return Qt::PermissionStatus::Undetermined;
167 return status;
168 }
169
170 void requestPermission(const QPermission &permission,
171 const QPermissions::Private::PermissionCallback &callback)
172 {
173 const auto nativePermissionList = nativeStringsFromPermission(permission);
174 if (nativePermissionList.isEmpty()) {
175 callback(Qt::PermissionStatus::Granted);
176 return;
177 }
178
179 QtAndroidPrivate::requestPermissions(nativePermissionList).then(qApp,
180 [callback, permission](QFuture<QtAndroidPrivate::PermissionResult> future) {
181 const auto androidResults = future.isValid() ? future.results()
182 : QList{QtAndroidPrivate::Denied};
183 const auto status = getCombinedStatus(androidResults);
184 g_permissionStatusHash->insert(permission.type().id(), status);
185 callback(status);
186 }
187 );
188 }
189}
190
191QT_END_NAMESPACE
Access Bluetooth peripherals.
Access the user's location.
\inmodule QtCore \inheaderfile QPermissions
void requestPermission(const QPermission &permission, const PermissionCallback &callback)
Qt::PermissionStatus checkPermission(const QPermission &permission)
Combined button and popup list for selecting options.
static QStringList nativeBluetoothPermission(const QBluetoothPermission &permission)
static Qt::PermissionStatus permissionStatusForAndroidResult(QtAndroidPrivate::PermissionResult result)
static Qt::PermissionStatus getCombinedStatus(const QList< QtAndroidPrivate::PermissionResult > &androidResults)
static QStringList nativeStringsFromPermission(const QPermission &permission)
static QStringList nativeLocationPermission(const QLocationPermission &permission)
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 } }))