From 5604f633c06585246ba473b209c35db67d68740c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tor=20Arne=20Vestb=C3=B8?= Date: Thu, 22 Jul 2021 14:34:38 +0200 Subject: [PATCH] Move Android implementation of permissions API to QtAndroidPrivate And remove plumbing from public QCoreApplication API, which is going to be removed in follow up patch after leaf modules have moved to the private Android API. The public permissions API will be reintroduced in 6.3 after further work. Pick-to: 6.2 Change-Id: I46772284b98d0ced8d4a624a850adaa4a1dfe645 Reviewed-by: Assam Boudjelthia --- src/corelib/CMakeLists.txt | 1 - src/corelib/kernel/qcoreapplication.cpp | 2 - .../kernel/qcoreapplication_android.cpp | 300 ------------------ .../platform/android/qandroidextras.cpp | 252 +++++++++++++++ .../platform/android/qandroidextras_p.h | 40 +++ 5 files changed, 292 insertions(+), 303 deletions(-) delete mode 100644 src/corelib/kernel/qcoreapplication_android.cpp diff --git a/src/corelib/CMakeLists.txt b/src/corelib/CMakeLists.txt index 2df53a0a00c..d44011f6c35 100644 --- a/src/corelib/CMakeLists.txt +++ b/src/corelib/CMakeLists.txt @@ -981,7 +981,6 @@ qt_internal_extend_target(Core CONDITION UNIX AND NOT APPLE qt_internal_extend_target(Core CONDITION ANDROID AND NOT ANDROID_EMBEDDED SOURCES io/qstandardpaths_android.cpp - kernel/qcoreapplication_android.cpp io/qstorageinfo_unix.cpp kernel/qjnienvironment.cpp kernel/qjnienvironment.h kernel/qjniobject.cpp kernel/qjniobject.h diff --git a/src/corelib/kernel/qcoreapplication.cpp b/src/corelib/kernel/qcoreapplication.cpp index 8f53790cb79..10dbe2f6843 100644 --- a/src/corelib/kernel/qcoreapplication.cpp +++ b/src/corelib/kernel/qcoreapplication.cpp @@ -3084,7 +3084,6 @@ void QCoreApplication::setEventDispatcher(QAbstractEventDispatcher *eventDispatc \sa Q_OBJECT, QObject::tr() */ #if QT_CONFIG(future) && !defined(QT_NO_QOBJECT) -#if !defined(Q_OS_ANDROID) QFuture defaultPermissionFuture() { @@ -3127,7 +3126,6 @@ void QCoreApplication::setEventDispatcher(QAbstractEventDispatcher *eventDispatc Q_UNUSED(permission) return defaultPermissionFuture(); } -#endif /*! Requests the \a permission and returns a QFuture representing the diff --git a/src/corelib/kernel/qcoreapplication_android.cpp b/src/corelib/kernel/qcoreapplication_android.cpp deleted file mode 100644 index 671db805e3b..00000000000 --- a/src/corelib/kernel/qcoreapplication_android.cpp +++ /dev/null @@ -1,300 +0,0 @@ -/**************************************************************************** -** -** 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"; - -QApplicationPermission::PermissionResult resultFromAndroid(jint value) -{ - return value == 0 ? QApplicationPermission::Authorized : QApplicationPermission::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(QApplicationPermission::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 QApplicationPermission::Location: - return {coarsePerm}; - case QApplicationPermission::PreciseLocation: - return {precisePerm}; - case QApplicationPermission::BackgroundLocation: - // 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 QApplicationPermission::PreciseBackgroundLocation: - // Keep the background permission first to be able to use .first() - // in checkPermission because it takes single permission - if (QtAndroidPrivate::androidSdkVersion() >= 29) - return {backgroundPerm, precisePerm}; - return {precisePerm}; - case QApplicationPermission::Camera: - return {QStringLiteral("android.permission.CAMERA")}; - case QApplicationPermission::Microphone: - return {QStringLiteral("android.permission.RECORD_AUDIO")}; - case QApplicationPermission::Bluetooth: - return { QStringLiteral("android.permission.BLUETOOTH") }; - case QApplicationPermission::BodySensors: - return {QStringLiteral("android.permission.BODY_SENSORS")}; - case QApplicationPermission::PhysicalActivity: - return {QStringLiteral("android.permission.ACTIVITY_RECOGNITION")}; - case QApplicationPermission::Contacts: - return {QStringLiteral("android.permission.READ_CONTACTS"), - QStringLiteral("android.permission.WRITE_CONTACTS")}; - case QApplicationPermission::Storage: - return {QStringLiteral("android.permission.READ_EXTERNAL_STORAGE"), - QStringLiteral("android.permission.WRITE_EXTERNAL_STORAGE")}; - case QApplicationPermission::Calendar: - return {QStringLiteral("android.permission.READ_CALENDAR"), - QStringLiteral("android.permission.WRITE_CALENDAR")}; - } - - 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) { - QApplicationPermission::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(); - promise.addResult(QApplicationPermission::Denied); - promise.finish(); - return future; -} - -static bool isBackgroundLocationApi29(QApplicationPermission::PermissionType permission) -{ - return QNativeInterface::QAndroidApplication::sdkVersion() >= 29 - && (permission == QApplicationPermission::BackgroundLocation - || permission == QApplicationPermission::PreciseBackgroundLocation); -} - -QFuture -QCoreApplicationPrivate::requestPermission(QApplicationPermission::PermissionType permission) -{ - QSharedPointer> promise; - promise.reset(new QPromise()); - QFuture future = promise->future(); - promise->start(); - const auto nativePermissions = nativeStringsFromPermission(permission); - - if (nativePermissions.size() > 0) { - requestPermissionsInternal(nativePermissions).then( - [promise, permission](QFuture future) { - auto AuthorizedCount = future.results().count(QApplicationPermission::Authorized); - if (AuthorizedCount > 0) { - if (isBackgroundLocationApi29(permission)) - promise->addResult(future.resultAt(0), 0); - else - promise->addResult(QApplicationPermission::Authorized, 0); - } else { - promise->addResult(QApplicationPermission::Denied, 0); - } - promise->finish(); - }); - - return future; - } - - promise->addResult(QApplicationPermission::Denied); - 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 { - promise.addResult(QApplicationPermission::Denied); - } - - promise.finish(); - return future; -} - -QFuture -QCoreApplicationPrivate::checkPermission(QApplicationPermission::PermissionType permission) -{ - const auto nativePermissions = nativeStringsFromPermission(permission); - - if (nativePermissions.size() > 0) - return checkPermission(nativePermissions.first()); - - QPromise promise; - QFuture future = promise.future(); - promise.start(); - promise.addResult(QApplicationPermission::Denied); - 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 diff --git a/src/corelib/platform/android/qandroidextras.cpp b/src/corelib/platform/android/qandroidextras.cpp index 6d79e6c1e99..e43800e27cd 100644 --- a/src/corelib/platform/android/qandroidextras.cpp +++ b/src/corelib/platform/android/qandroidextras.cpp @@ -47,6 +47,10 @@ #include #include +#if QT_CONFIG(future) +#include +#endif + QT_BEGIN_NAMESPACE class QAndroidParcelPrivate @@ -1031,4 +1035,252 @@ void QAndroidActivityCallbackResultReceiver::registerCallback( callbackMap.insert(receiverRequestCode, callbackFunc); } +// Permissions API + +static const char qtNativeClassName[] = "org/qtproject/qt/android/QtNative"; + +QtAndroidPrivate::PermissionResult resultFromAndroid(jint value) +{ + return value == 0 ? QtAndroidPrivate::Authorized : QtAndroidPrivate::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(QtAndroidPrivate::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 QtAndroidPrivate::Location: + return {coarsePerm}; + case QtAndroidPrivate::PreciseLocation: + return {precisePerm}; + case QtAndroidPrivate::BackgroundLocation: + // 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 QtAndroidPrivate::PreciseBackgroundLocation: + // Keep the background permission first to be able to use .first() + // in checkPermission because it takes single permission + if (QtAndroidPrivate::androidSdkVersion() >= 29) + return {backgroundPerm, precisePerm}; + return {precisePerm}; + case QtAndroidPrivate::Camera: + return {QStringLiteral("android.permission.CAMERA")}; + case QtAndroidPrivate::Microphone: + return {QStringLiteral("android.permission.RECORD_AUDIO")}; + case QtAndroidPrivate::Bluetooth: + return { QStringLiteral("android.permission.BLUETOOTH") }; + case QtAndroidPrivate::BodySensors: + return {QStringLiteral("android.permission.BODY_SENSORS")}; + case QtAndroidPrivate::PhysicalActivity: + return {QStringLiteral("android.permission.ACTIVITY_RECOGNITION")}; + case QtAndroidPrivate::Contacts: + return {QStringLiteral("android.permission.READ_CONTACTS"), + QStringLiteral("android.permission.WRITE_CONTACTS")}; + case QtAndroidPrivate::Storage: + return {QStringLiteral("android.permission.READ_EXTERNAL_STORAGE"), + QStringLiteral("android.permission.WRITE_EXTERNAL_STORAGE")}; + case QtAndroidPrivate::Calendar: + return {QStringLiteral("android.permission.READ_CALENDAR"), + QStringLiteral("android.permission.WRITE_CALENDAR")}; + } + + 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) { + QtAndroidPrivate::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(QtAndroidPrivate::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 +QtAndroidPrivate::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(); + promise.addResult(QtAndroidPrivate::Denied); + promise.finish(); + return future; +} + +static bool isBackgroundLocationApi29(QtAndroidPrivate::PermissionType permission) +{ + return QNativeInterface::QAndroidApplication::sdkVersion() >= 29 + && (permission == QtAndroidPrivate::BackgroundLocation + || permission == QtAndroidPrivate::PreciseBackgroundLocation); +} + +QFuture +QtAndroidPrivate::requestPermission(QtAndroidPrivate::PermissionType permission) +{ + QSharedPointer> promise; + promise.reset(new QPromise()); + QFuture future = promise->future(); + promise->start(); + const auto nativePermissions = nativeStringsFromPermission(permission); + + if (nativePermissions.size() > 0) { + requestPermissionsInternal(nativePermissions).then( + [promise, permission](QFuture future) { + auto AuthorizedCount = future.results().count(QtAndroidPrivate::Authorized); + if (AuthorizedCount > 0) { + if (isBackgroundLocationApi29(permission)) + promise->addResult(future.resultAt(0), 0); + else + promise->addResult(QtAndroidPrivate::Authorized, 0); + } else { + promise->addResult(QtAndroidPrivate::Denied, 0); + } + promise->finish(); + }); + + return future; + } + + promise->addResult(QtAndroidPrivate::Denied); + promise->finish(); + return future; +} + +QFuture +QtAndroidPrivate::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 { + promise.addResult(QtAndroidPrivate::Denied); + } + + promise.finish(); + return future; +} + +QFuture +QtAndroidPrivate::checkPermission(QtAndroidPrivate::PermissionType permission) +{ + const auto nativePermissions = nativeStringsFromPermission(permission); + + if (nativePermissions.size() > 0) + return checkPermission(nativePermissions.first()); + + QPromise promise; + QFuture future = promise.future(); + promise.start(); + promise.addResult(QtAndroidPrivate::Denied); + 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 diff --git a/src/corelib/platform/android/qandroidextras_p.h b/src/corelib/platform/android/qandroidextras_p.h index 464c7f8aea6..587c8661d69 100644 --- a/src/corelib/platform/android/qandroidextras_p.h +++ b/src/corelib/platform/android/qandroidextras_p.h @@ -60,6 +60,10 @@ #include #include +#if QT_CONFIG(future) +#include +#endif + QT_BEGIN_NAMESPACE class QAndroidParcel; @@ -255,6 +259,42 @@ namespace QtAndroidPrivate Q_CORE_EXPORT bool bindService(const QAndroidIntent &serviceIntent, const QAndroidServiceConnection &serviceConnection, BindFlags flags = BindFlag::None); + +#if QT_CONFIG(future) + enum PermissionType { + Camera, + Microphone, + Bluetooth, + Location, + PreciseLocation, + BackgroundLocation, + PreciseBackgroundLocation, + BodySensors, + PhysicalActivity, + Contacts, + Storage, + // TODO: remove after usages in other modules are renamed. + WriteStorage, + Calendar + }; + + enum PermissionResult { + Undetermined, + Authorized, + Denied + }; + + Q_CORE_EXPORT QFuture + requestPermission(QtAndroidPrivate::PermissionType permission); + Q_CORE_EXPORT QFuture + requestPermission(const QString &permission); + + Q_CORE_EXPORT QFuture + checkPermission(QtAndroidPrivate::PermissionType permission); + Q_CORE_EXPORT QFuture + checkPermission(const QString &permission); +#endif + } QT_END_NAMESPACE