summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorBogDan Vatra <bogdan@kdab.com>2016-09-07 16:55:51 +0300
committerBogDan Vatra <bogdan@kdab.com>2016-09-16 12:13:29 +0000
commit31a63b69037c680101bf82a7161d4a0e5184dd86 (patch)
tree4d9c2aa085b6abf6e108e07518cb1446aee14328
parentfdca8cb09ee330813c41fd1bd20b2795a0a30e04 (diff)
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 <christian.stromme@qt.io> Reviewed-by: Alex Blasche <alexander.blasche@qt.io>
-rw-r--r--src/android/jar/src/org/qtproject/qt5/android/QtActivityDelegate.java5
-rw-r--r--src/android/jar/src/org/qtproject/qt5/android/QtNative.java19
-rw-r--r--src/android/java/src/org/qtproject/qt5/android/bindings/QtActivity.java7
-rw-r--r--src/android/java/src/org/qtproject/qt5/android/bindings/QtApplication.java1
-rw-r--r--src/corelib/kernel/qjnihelpers.cpp122
-rw-r--r--src/corelib/kernel/qjnihelpers_p.h11
6 files changed, 158 insertions, 7 deletions
diff --git a/src/android/jar/src/org/qtproject/qt5/android/QtActivityDelegate.java b/src/android/jar/src/org/qtproject/qt5/android/QtActivityDelegate.java
index b602cabd27..bfdbaed43f 100644
--- a/src/android/jar/src/org/qtproject/qt5/android/QtActivityDelegate.java
+++ b/src/android/jar/src/org/qtproject/qt5/android/QtActivityDelegate.java
@@ -1455,4 +1455,9 @@ public class QtActivityDelegate
}
return false;
}
+
+ public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults)
+ {
+ QtNative.sendRequestPermissionsResult(requestCode, permissions, grantResults);
+ }
}
diff --git a/src/android/jar/src/org/qtproject/qt5/android/QtNative.java b/src/android/jar/src/org/qtproject/qt5/android/QtNative.java
index 4df2cb88c9..6876aac11f 100644
--- a/src/android/jar/src/org/qtproject/qt5/android/QtNative.java
+++ b/src/android/jar/src/org/qtproject/qt5/android/QtNative.java
@@ -455,20 +455,25 @@ public class QtNative
}
}
- public static int checkSelfPermission(final String permission)
+ public static Context getContext() {
+ if (m_activity == null)
+ return m_activity;
+ return m_service;
+ }
+
+ public static int checkSelfPermission(String permission)
{
int perm = PackageManager.PERMISSION_DENIED;
synchronized (m_mainActivityMutex) {
- if (m_activity == null)
- return perm;
+ Context context = getContext();
try {
if (Build.VERSION.SDK_INT >= 23) {
if (m_checkSelfPermissionMethod == null)
m_checkSelfPermissionMethod = Context.class.getMethod("checkSelfPermission", String.class);
- perm = (Integer)m_checkSelfPermissionMethod.invoke(m_activity, permission);
+ perm = (Integer)m_checkSelfPermissionMethod.invoke(context, permission);
} else {
- final PackageManager pm = m_activity.getPackageManager();
- perm = pm.checkPermission(permission, m_activity.getPackageName());
+ final PackageManager pm = context.getPackageManager();
+ perm = pm.checkPermission(permission, context.getApplicationContext().getPackageName());
}
} catch (Exception e) {
e.printStackTrace();
@@ -831,6 +836,8 @@ public class QtNative
public static native void runPendingCppRunnables();
+ public static native void sendRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults);
+
private static native void setNativeActivity(Activity activity);
private static native void setNativeService(Service service);
}
diff --git a/src/android/java/src/org/qtproject/qt5/android/bindings/QtActivity.java b/src/android/java/src/org/qtproject/qt5/android/bindings/QtActivity.java
index 573a28e44b..22ff1738c8 100644
--- a/src/android/java/src/org/qtproject/qt5/android/bindings/QtActivity.java
+++ b/src/android/java/src/org/qtproject/qt5/android/bindings/QtActivity.java
@@ -981,4 +981,11 @@ public class QtActivity extends Activity
//---------------------------------------------------------------------------
//@ANDROID-12
+ public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults)
+ {
+ if (QtApplication.m_delegateObject != null && QtApplication.onRequestPermissionsResult != null) {
+ QtApplication.invokeDelegateMethod(QtApplication.onRequestPermissionsResult, requestCode , permissions, grantResults);
+ return;
+ }
+ }
}
diff --git a/src/android/java/src/org/qtproject/qt5/android/bindings/QtApplication.java b/src/android/java/src/org/qtproject/qt5/android/bindings/QtApplication.java
index 1078060d7f..afc0432bdd 100644
--- a/src/android/java/src/org/qtproject/qt5/android/bindings/QtApplication.java
+++ b/src/android/java/src/org/qtproject/qt5/android/bindings/QtApplication.java
@@ -64,6 +64,7 @@ public class QtApplication extends Application
public static Method onKeyShortcut = null;
public static Method dispatchGenericMotionEvent = null;
public static Method onGenericMotionEvent = null;
+ public static Method onRequestPermissionsResult = null;
private static String activityClassName;
public static void setQtContextDelegate(Class<?> clazz, Object listener)
{
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 <QtCore/qrunnable.h>
#include <deque>
+#include <memory>
QT_BEGIN_NAMESPACE
@@ -60,6 +63,22 @@ static jmethodID g_hideSplashScreenMethodID = Q_NULLPTR;
Q_GLOBAL_STATIC(std::deque<QtAndroidPrivate::Runnable>, 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<int, QSharedPointer<PermissionsResultClass>> 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<QtAndroidPrivate::GenericMotionEventListener *> 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<jint[]> 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<void *>(dispatchGenericMotionEvent)},
{"dispatchKeyEvent", "(Landroid/view/KeyEvent;)Z", reinterpret_cast<void *>(dispatchKeyEvent)},
{"setNativeActivity", "(Landroid/app/Activity;)V", reinterpret_cast<void *>(setNativeActivity)},
- {"setNativeService", "(Landroid/app/Service;)V", reinterpret_cast<void *>(setNativeService)}
+ {"setNativeService", "(Landroid/app/Service;)V", reinterpret_cast<void *>(setNativeService)},
+ {"sendRequestPermissionsResult", "(I[Ljava/lang/String;[I)V", reinterpret_cast<void *>(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<QString, QtAndroidPrivate::PermissionsResult> 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<PermissionsResultClass>::create(callbackFunc);
+ g_pendingPermissionRequestsMutex->unlock();
+ }
+
+ runOnAndroidThread([permissions, callbackFunc, requestCode, directCall] {
+ if (directCall) {
+ g_pendingPermissionRequestsMutex->lock();
+ (*g_pendingPermissionRequests)[requestCode] = QSharedPointer<PermissionsResultClass>::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<void>("requestPermissions", "([Ljava/lang/String;I)V", array, requestCode);
+ env->DeleteLocalRef(array);
+ }, env);
+}
+
+QHash<QString, QtAndroidPrivate::PermissionsResult> QtAndroidPrivate::requestPermissionsSync(JNIEnv *env, const QStringList &permissions, int timeoutMs)
+{
+ QSharedPointer<QHash<QString, QtAndroidPrivate::PermissionsResult>> res(new QHash<QString, QtAndroidPrivate::PermissionsResult>());
+ QSharedPointer<QSemaphore> sem(new QSemaphore);
+ requestPermissions(env, permissions, [sem, res](const QHash<QString, PermissionsResult> &result){
+ *res = result;
+ sem->release();
+ }, true);
+ sem->tryAcquire(1, timeoutMs);
+ return *res;
+}
+
+QtAndroidPrivate::PermissionsResult QtAndroidPrivate::checkPermission(const QString &permission)
+{
+ const auto res = QJNIObjectPrivate::callStaticMethod<jint>("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<jboolean>("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<QString, QtAndroidPrivate::PermissionsResult> PermissionsHash;
typedef std::function<void()> Runnable;
+ typedef std::function<void(const PermissionsHash &)> 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<QString, PermissionsResult> 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);