/**************************************************************************** ** ** Copyright (C) 2021 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the QtGui module of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:LGPL$ ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and The Qt Company. For licensing terms ** and conditions see https://www.qt.io/terms-conditions. For further ** information use the contact form at https://www.qt.io/contact-us. ** ** GNU Lesser General Public License Usage ** Alternatively, this file may be used under the terms of the GNU Lesser ** General Public License version 3 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL3 included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 3 requirements ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 2.0 or (at your option) the GNU General ** Public license version 3 or any later version approved by the KDE Free ** Qt Foundation. The licenses are as published by the Free Software ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 ** included in the packaging of this file. Please review the following ** information to ensure the GNU General Public License requirements will ** be met: https://www.gnu.org/licenses/gpl-2.0.html and ** https://www.gnu.org/licenses/gpl-3.0.html. ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #include "qcoreapplication.h" #include "qcoreapplication_p.h" #include #include #include #include #include #include #include #include QT_BEGIN_NAMESPACE static const char qtNativeClassName[] = "org/qtproject/qt/android/QtNative"; #ifndef QT_NO_EXCEPTIONS static const char emptyPermissionExcept[] = "The permission cannot be an empty string."; static const char invalidNativePermissionExcept[] = "Coudn't convert the provided permission type to a native Android permission string."; #endif QPermission::PermissionResult resultFromAndroid(jint value) { return value == 0 ? QPermission::Authorized : QPermission::Denied; } using PendingPermissionRequestsHash = QHash>>; Q_GLOBAL_STATIC(PendingPermissionRequestsHash, g_pendingPermissionRequests); static QBasicMutex g_pendingPermissionRequestsMutex; static int nextRequestCode() { static QBasicAtomicInt counter = Q_BASIC_ATOMIC_INITIALIZER(0); return counter.fetchAndAddRelaxed(1); } static QStringList nativeStringsFromPermission(QPermission::PermissionType permission) { static const auto precisePerm = QStringLiteral("android.permission.ACCESS_FINE_LOCATION"); static const auto coarsePerm = QStringLiteral("android.permission.ACCESS_COARSE_LOCATION"); static const auto backgroundPerm = QStringLiteral("android.permission.ACCESS_BACKGROUND_LOCATION"); switch (permission) { case QPermission::CoarseLocation: return {coarsePerm}; case QPermission::PreciseLocation: return {precisePerm}; case QPermission::CoarseBackgroundLocation: // Keep the background permission first to be able to use .first() // in checkPermission because it takes single permission if (QtAndroidPrivate::androidSdkVersion() >= 29) return {backgroundPerm, coarsePerm}; return {coarsePerm}; case QPermission::PreciseBackgroundLocation: if (QtAndroidPrivate::androidSdkVersion() >= 29) return {backgroundPerm, precisePerm}; return {precisePerm}; case QPermission::Camera: return {QStringLiteral("android.permission.CAMERA")}; case QPermission::Microphone: return {QStringLiteral("android.permission.RECORD_AUDIO")}; case QPermission::BodySensors: return {QStringLiteral("android.permission.BODY_SENSORS")}; case QPermission::PhysicalActivity: return {QStringLiteral("android.permission.ACTIVITY_RECOGNITION")}; case QPermission::ReadContacts: return {QStringLiteral("android.permission.READ_CONTACTS")}; case QPermission::WriteContacts: return {QStringLiteral("android.permission.WRITE_CONTACTS")}; case QPermission::ReadStorage: return {QStringLiteral("android.permission.READ_EXTERNAL_STORAGE")}; case QPermission::WriteStorage: return {QStringLiteral("android.permission.WRITE_EXTERNAL_STORAGE")}; case QPermission::ReadCalendar: return {QStringLiteral("android.permission.READ_CALENDAR")}; case QPermission::WriteCalendar: return {QStringLiteral("android.permission.WRITE_CALENDAR")}; default: return {}; } } /*! \internal This function is called when the result of the permission request is available. Once a permission is requested, the result is braodcast by the OS and listened to by QtActivity which passes it to C++ through a native JNI method call. */ static void sendRequestPermissionsResult(JNIEnv *env, jobject *obj, jint requestCode, jobjectArray permissions, jintArray grantResults) { Q_UNUSED(obj); QMutexLocker locker(&g_pendingPermissionRequestsMutex); auto it = g_pendingPermissionRequests->constFind(requestCode); if (it == g_pendingPermissionRequests->constEnd()) { qWarning() << "Found no valid pending permission request for request code" << requestCode; return; } auto request = *it; g_pendingPermissionRequests->erase(it); locker.unlock(); const int size = env->GetArrayLength(permissions); std::unique_ptr results(new jint[size]); env->GetIntArrayRegion(grantResults, 0, size, results.get()); for (int i = 0 ; i < size; ++i) { QPermission::PermissionResult result = resultFromAndroid(results[i]); request->addResult(result, i); } request->finish(); } QFuture requestPermissionsInternal(const QStringList &permissions) { QSharedPointer> promise; promise.reset(new QPromise()); QFuture future = promise->future(); promise->start(); // No mechanism to request permission for SDK version below 23, because // permissions defined in the manifest are granted at install time. if (QtAndroidPrivate::androidSdkVersion() < 23) { for (int i = 0; i < permissions.size(); ++i) promise->addResult(QCoreApplication::checkPermission(permissions.at(i)).result(), i); promise->finish(); return future; } const int requestCode = nextRequestCode(); QMutexLocker locker(&g_pendingPermissionRequestsMutex); g_pendingPermissionRequests->insert(requestCode, promise); QNativeInterface::QAndroidApplication::runOnAndroidMainThread([permissions, requestCode] { QJniEnvironment env; jclass clazz = env.findClass("java/lang/String"); auto array = env->NewObjectArray(permissions.size(), clazz, nullptr); int index = 0; for (auto &perm : permissions) env->SetObjectArrayElement(array, index++, QJniObject::fromString(perm).object()); QJniObject(QtAndroidPrivate::activity()).callMethod("requestPermissions", "([Ljava/lang/String;I)V", array, requestCode); env->DeleteLocalRef(array); }); return future; } QFuture QCoreApplicationPrivate::requestPermission(const QString &permission) { // avoid the uneccessary call and response to an empty permission string if (permission.size() > 0) return requestPermissionsInternal({permission}); QPromise promise; QFuture future = promise.future(); promise.start(); #ifndef QT_NO_EXCEPTIONS promise.setException(std::make_exception_ptr(std::runtime_error(emptyPermissionExcept))); #else promise.addResult(QPermission::Denied); #endif promise.finish(); return future; } QFuture groupRequestToSingleResult(const QStringList &permissions) { QSharedPointer> promise; promise.reset(new QPromise()); QFuture future = promise->future(); promise->start(); requestPermissionsInternal(permissions).then( [promise](QFuture future) { auto results = future.results(); if (results.count(QPermission::Authorized) == results.count()) promise->addResult(QPermission::Authorized, 0); else promise->addResult(QPermission::Denied, 0); promise->finish(); }); return future; } QFuture QCoreApplicationPrivate::requestPermission(QPermission::PermissionType permission) { const auto nativePermissions = nativeStringsFromPermission(permission); if (nativePermissions.size() > 0) return groupRequestToSingleResult(nativePermissions); QPromise promise; QFuture future = promise.future(); promise.start(); #ifndef QT_NO_EXCEPTIONS promise.setException(std::make_exception_ptr( std::runtime_error(invalidNativePermissionExcept))); #else promise.addResult(QPermission::Denied); #endif promise.finish(); return future; } QFuture QCoreApplicationPrivate::checkPermission(const QString &permission) { QPromise promise; QFuture future = promise.future(); promise.start(); if (permission.size() > 0) { auto res = QJniObject::callStaticMethod(qtNativeClassName, "checkSelfPermission", "(Ljava/lang/String;)I", QJniObject::fromString(permission).object()); promise.addResult(resultFromAndroid(res)); } else { #ifndef QT_NO_EXCEPTIONS promise.setException(std::make_exception_ptr(std::runtime_error(emptyPermissionExcept))); #else promise.addResult(QPermission::Denied); #endif } promise.finish(); return future; } QFuture QCoreApplicationPrivate::checkPermission(QPermission::PermissionType permission) { const auto nativePermissions = nativeStringsFromPermission(permission); if (nativePermissions.size() > 0) return checkPermission(nativePermissions.first()); QPromise promise; QFuture future = promise.future(); promise.start(); #ifndef QT_NO_EXCEPTIONS promise.setException(std::make_exception_ptr( std::runtime_error(invalidNativePermissionExcept))); #else promise.addResult(QPermission::Denied); #endif promise.finish(); return future; } bool QtAndroidPrivate::registerPermissionNatives() { if (QtAndroidPrivate::androidSdkVersion() < 23) return true; JNINativeMethod methods[] = { {"sendRequestPermissionsResult", "(I[Ljava/lang/String;[I)V", reinterpret_cast(sendRequestPermissionsResult) }}; QJniEnvironment env; return env.registerNativeMethods(qtNativeClassName, methods, 1); } QT_END_NAMESPACE