From 31a63b69037c680101bf82a7161d4a0e5184dd86 Mon Sep 17 00:00:00 2001 From: BogDan Vatra Date: Wed, 7 Sep 2016 16:55:51 +0300 Subject: Say hello to Android 6+ permissions model This API was introduced by Android v23 and is used in connection with the new permission request system. From Android v23 onwards, some permissions such as Location permissions cannot only be granted via Android's Manifest files. An additional runtime check/request system was introduced which forces applications to prompt the user the first time a privileged function is called. Such user prompt responses are returned to the current application via the Activity.onRequestPermissionsResult(..) callback. This change add Qt API to nicely check & request permissions. For now this is private API which can be used to fix permission problems in other Qt compontents. Later Qt versions will introduce a public API to QtAndroidExtras. [ChangeLog][QtCore][Android] Introduced a mechanism to forward permission related callbacks on Activity objects to interested parties. Task-number: QTBUG-55035 Task-number: QTBUG-50759 Change-Id: I64ee748d741b39e35c4713ed9fdd15dd1d96dc56 Reviewed-by: Christian Stromme Reviewed-by: Alex Blasche --- src/corelib/kernel/qjnihelpers.cpp | 122 ++++++++++++++++++++++++++++++++++++- src/corelib/kernel/qjnihelpers_p.h | 11 ++++ 2 files changed, 132 insertions(+), 1 deletion(-) (limited to 'src/corelib') diff --git a/src/corelib/kernel/qjnihelpers.cpp b/src/corelib/kernel/qjnihelpers.cpp index 102b835089..6a46f7dd11 100644 --- a/src/corelib/kernel/qjnihelpers.cpp +++ b/src/corelib/kernel/qjnihelpers.cpp @@ -38,14 +38,17 @@ ****************************************************************************/ #include "qjnihelpers_p.h" +#include "qjni_p.h" #include "qmutex.h" #include "qlist.h" #include "qsemaphore.h" #include "qsharedpointer.h" #include "qvector.h" +#include "qthread.h" #include #include +#include QT_BEGIN_NAMESPACE @@ -60,6 +63,22 @@ static jmethodID g_hideSplashScreenMethodID = Q_NULLPTR; Q_GLOBAL_STATIC(std::deque, g_pendingRunnables); Q_GLOBAL_STATIC(QMutex, g_pendingRunnablesMutex); +class PermissionsResultClass : public QObject +{ + Q_OBJECT +public: + PermissionsResultClass(const QtAndroidPrivate::PermissionsResultFunc &func) : m_func(func) {} + Q_INVOKABLE void sendResult(const QtAndroidPrivate::PermissionsHash &result) { m_func(result); } + +private: + QtAndroidPrivate::PermissionsResultFunc m_func; +}; + +typedef QHash> PendingPermissionRequestsHash; +Q_GLOBAL_STATIC(PendingPermissionRequestsHash, g_pendingPermissionRequests); +Q_GLOBAL_STATIC(QMutex, g_pendingPermissionRequestsMutex); +Q_GLOBAL_STATIC(QAtomicInt, g_requestPermissionsRequestCode); + // function called from Java from Android UI thread static void runPendingCppRunnables(JNIEnv */*env*/, jobject /*obj*/) { @@ -81,9 +100,43 @@ namespace { QMutex mutex; QVector listeners; }; + + enum { + PERMISSION_GRANTED = 0 + }; } Q_GLOBAL_STATIC(GenericMotionEventListeners, g_genericMotionEventListeners) +static void sendRequestPermissionsResult(JNIEnv *env, jobject /*obj*/, jint requestCode, + jobjectArray permissions, jintArray grantResults) +{ + g_pendingPermissionRequestsMutex->lock(); + auto it = g_pendingPermissionRequests->find(requestCode); + if (it == g_pendingPermissionRequests->end()) { + g_pendingPermissionRequestsMutex->unlock(); + // show an error or something ? + return; + } + g_pendingPermissionRequestsMutex->unlock(); + + Qt::ConnectionType connection = QThread::currentThread() == it.value()->thread() ? Qt::DirectConnection : Qt::BlockingQueuedConnection; + QtAndroidPrivate::PermissionsHash hash; + 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) { + const auto &permission = QJNIObjectPrivate(env->GetObjectArrayElement(permissions, i)).toString(); + auto value = results[i] == PERMISSION_GRANTED ? + QtAndroidPrivate::PermissionsResult::Granted : + QtAndroidPrivate::PermissionsResult::Denied; + hash[permission] = value; + } + QMetaObject::invokeMethod(it.value().data(), "sendResult", connection, Q_ARG(QtAndroidPrivate::PermissionsHash, hash)); + g_pendingPermissionRequestsMutex->lock(); + g_pendingPermissionRequests->erase(it); + g_pendingPermissionRequestsMutex->unlock(); +} + static jboolean dispatchGenericMotionEvent(JNIEnv *, jclass, jobject event) { jboolean ret = JNI_FALSE; @@ -328,7 +381,8 @@ jint QtAndroidPrivate::initJNI(JavaVM *vm, JNIEnv *env) {"dispatchGenericMotionEvent", "(Landroid/view/MotionEvent;)Z", reinterpret_cast(dispatchGenericMotionEvent)}, {"dispatchKeyEvent", "(Landroid/view/KeyEvent;)Z", reinterpret_cast(dispatchKeyEvent)}, {"setNativeActivity", "(Landroid/app/Activity;)V", reinterpret_cast(setNativeActivity)}, - {"setNativeService", "(Landroid/app/Service;)V", reinterpret_cast(setNativeService)} + {"setNativeService", "(Landroid/app/Service;)V", reinterpret_cast(setNativeService)}, + {"sendRequestPermissionsResult", "(I[Ljava/lang/String;[I)V", reinterpret_cast(sendRequestPermissionsResult)}, }; const bool regOk = (env->RegisterNatives(jQtNative, methods, sizeof(methods) / sizeof(methods[0])) == JNI_OK); @@ -411,6 +465,70 @@ void QtAndroidPrivate::runOnAndroidThreadSync(const QtAndroidPrivate::Runnable & sem->tryAcquire(1, timeoutMs); } +void QtAndroidPrivate::requestPermissions(JNIEnv *env, const QStringList &permissions, const QtAndroidPrivate::PermissionsResultFunc &callbackFunc, bool directCall) +{ + if (androidSdkVersion() < 23 || !activity()) { + QHash res; + for (const auto &perm : permissions) + res[perm] = checkPermission(perm); + callbackFunc(res); + return; + } + // Check API 23+ permissions + const int requestCode = (*g_requestPermissionsRequestCode)++; + if (!directCall) { + g_pendingPermissionRequestsMutex->lock(); + (*g_pendingPermissionRequests)[requestCode] = QSharedPointer::create(callbackFunc); + g_pendingPermissionRequestsMutex->unlock(); + } + + runOnAndroidThread([permissions, callbackFunc, requestCode, directCall] { + if (directCall) { + g_pendingPermissionRequestsMutex->lock(); + (*g_pendingPermissionRequests)[requestCode] = QSharedPointer::create(callbackFunc); + g_pendingPermissionRequestsMutex->unlock(); + } + + QJNIEnvironmentPrivate env; + auto array = env->NewObjectArray(permissions.size(), env->FindClass("java/lang/String"), nullptr); + int index = 0; + for (const auto &perm : permissions) + env->SetObjectArrayElement(array, index++, QJNIObjectPrivate::fromString(perm).object()); + QJNIObjectPrivate(activity()).callMethod("requestPermissions", "([Ljava/lang/String;I)V", array, requestCode); + env->DeleteLocalRef(array); + }, env); +} + +QHash QtAndroidPrivate::requestPermissionsSync(JNIEnv *env, const QStringList &permissions, int timeoutMs) +{ + QSharedPointer> res(new QHash()); + QSharedPointer sem(new QSemaphore); + requestPermissions(env, permissions, [sem, res](const QHash &result){ + *res = result; + sem->release(); + }, true); + sem->tryAcquire(1, timeoutMs); + return *res; +} + +QtAndroidPrivate::PermissionsResult QtAndroidPrivate::checkPermission(const QString &permission) +{ + const auto res = QJNIObjectPrivate::callStaticMethod("org/qtproject/qt5/android/QtNative", + "checkSelfPermission", + "(Ljava/lang/String;)I", + QJNIObjectPrivate::fromString(permission).object()); + return res == PERMISSION_GRANTED ? PermissionsResult::Granted : PermissionsResult::Denied; +} + +bool QtAndroidPrivate::shouldShowRequestPermissionRationale(const QString &permission) +{ + if (androidSdkVersion() < 23 || !activity()) + return false; + + return QJNIObjectPrivate(activity()).callMethod("shouldShowRequestPermissionRationale", "(Ljava/lang/String;)Z", + QJNIObjectPrivate::fromString(permission).object()); +} + void QtAndroidPrivate::registerGenericMotionEventListener(QtAndroidPrivate::GenericMotionEventListener *listener) { QMutexLocker locker(&g_genericMotionEventListeners()->mutex); @@ -441,3 +559,5 @@ void QtAndroidPrivate::hideSplashScreen(JNIEnv *env) } QT_END_NAMESPACE + +#include "qjnihelpers.moc" diff --git a/src/corelib/kernel/qjnihelpers_p.h b/src/corelib/kernel/qjnihelpers_p.h index 43e2f3af20..478f62a5c7 100644 --- a/src/corelib/kernel/qjnihelpers_p.h +++ b/src/corelib/kernel/qjnihelpers_p.h @@ -58,6 +58,7 @@ QT_BEGIN_NAMESPACE class QRunnable; +class QStringList; namespace QtAndroidPrivate { @@ -97,7 +98,13 @@ namespace QtAndroidPrivate virtual bool handleKeyEvent(jobject event) = 0; }; + enum class PermissionsResult { + Granted, + Denied + }; + typedef QHash PermissionsHash; typedef std::function Runnable; + typedef std::function PermissionsResultFunc; Q_CORE_EXPORT jobject activity(); Q_CORE_EXPORT jobject service(); @@ -109,6 +116,10 @@ namespace QtAndroidPrivate Q_CORE_EXPORT void runOnAndroidThread(const Runnable &runnable, JNIEnv *env); Q_CORE_EXPORT void runOnAndroidThreadSync(const Runnable &runnable, JNIEnv *env, int timeoutMs = INT_MAX); Q_CORE_EXPORT void runOnUiThread(QRunnable *runnable, JNIEnv *env); + Q_CORE_EXPORT void requestPermissions(JNIEnv *env, const QStringList &permissions, const PermissionsResultFunc &callbackFunc, bool directCall = false); + Q_CORE_EXPORT QHash requestPermissionsSync(JNIEnv *env, const QStringList &permissions, int timeoutMs = INT_MAX); + Q_CORE_EXPORT PermissionsResult checkPermission(const QString &permission); + Q_CORE_EXPORT bool shouldShowRequestPermissionRationale(const QString &permission); Q_CORE_EXPORT void handleActivityResult(jint requestCode, jint resultCode, jobject data); Q_CORE_EXPORT void registerActivityResultListener(ActivityResultListener *listener); -- cgit v1.2.3