summaryrefslogtreecommitdiffstats
path: root/src/corelib/kernel/qjnihelpers.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/corelib/kernel/qjnihelpers.cpp')
-rw-r--r--src/corelib/kernel/qjnihelpers.cpp204
1 files changed, 201 insertions, 3 deletions
diff --git a/src/corelib/kernel/qjnihelpers.cpp b/src/corelib/kernel/qjnihelpers.cpp
index b2a5dc7441..46143d4c2c 100644
--- a/src/corelib/kernel/qjnihelpers.cpp
+++ b/src/corelib/kernel/qjnihelpers.cpp
@@ -37,14 +37,18 @@
**
****************************************************************************/
-#include "qjnihelpers_p.h"
-
+#include "qcoreapplication.h"
#include "qjnienvironment.h"
+#include "qjnihelpers_p.h"
#include "qjniobject.h"
#include "qlist.h"
#include "qmutex.h"
#include "qsemaphore.h"
+#include "qsharedpointer.h"
+#include "qthread.h"
+
#include <QtCore/private/qcoreapplication_p.h>
+#include <QtCore/qrunnable.h>
#include <android/log.h>
#include <deque>
@@ -68,20 +72,91 @@ static JavaVM *g_javaVM = nullptr;
static jobject g_jActivity = nullptr;
static jobject g_jService = nullptr;
static jobject g_jClassLoader = nullptr;
+static jclass g_jNativeClass = nullptr;
+static jmethodID g_runPendingCppRunnablesMethodID = nullptr;
+Q_GLOBAL_STATIC(std::deque<QtAndroidPrivate::Runnable>, g_pendingRunnables);
+static QBasicMutex g_pendingRunnablesMutex;
Q_GLOBAL_STATIC_WITH_ARGS(QtAndroidPrivate::OnBindListener*, g_onBindListener, (nullptr));
Q_GLOBAL_STATIC(QMutex, g_onBindListenerMutex);
Q_GLOBAL_STATIC(QSemaphore, g_waitForServiceSetupSemaphore);
Q_GLOBAL_STATIC(QAtomicInt, g_serviceSetupLockers);
+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); delete this;}
+
+private:
+ QtAndroidPrivate::PermissionsResultFunc m_func;
+};
+
+typedef QHash<int, PermissionsResultClass*> PendingPermissionRequestsHash;
+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);
+}
+
+// function called from Java from Android UI thread
+static void runPendingCppRunnables(JNIEnv */*env*/, jobject /*obj*/)
+{
+ for (;;) { // run all posted runnables
+ QMutexLocker locker(&g_pendingRunnablesMutex);
+ if (g_pendingRunnables->empty()) {
+ break;
+ }
+ QtAndroidPrivate::Runnable runnable(std::move(g_pendingRunnables->front()));
+ g_pendingRunnables->pop_front();
+ locker.unlock();
+ runnable(); // run it outside the sync block!
+ }
+}
+
namespace {
struct GenericMotionEventListeners {
QMutex mutex;
QList<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)
+{
+ QMutexLocker locker(&g_pendingPermissionRequestsMutex);
+ auto it = g_pendingPermissionRequests->find(requestCode);
+ if (it == g_pendingPermissionRequests->end()) {
+ // show an error or something ?
+ return;
+ }
+ auto request = *it;
+ g_pendingPermissionRequests->erase(it);
+ locker.unlock();
+
+ Qt::ConnectionType connection = QThread::currentThread() == request->thread() ? Qt::DirectConnection : Qt::QueuedConnection;
+ 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 = QJniObject(env->GetObjectArrayElement(permissions, i)).toString();
+ auto value = results[i] == PERMISSION_GRANTED ?
+ QtAndroidPrivate::PermissionsResult::Granted :
+ QtAndroidPrivate::PermissionsResult::Denied;
+ hash[permission] = value;
+ }
+ QMetaObject::invokeMethod(request, "sendResult", connection, Q_ARG(QtAndroidPrivate::PermissionsHash, hash));
+}
+
static jboolean dispatchGenericMotionEvent(JNIEnv *, jclass, jobject event)
{
jboolean ret = JNI_FALSE;
@@ -269,12 +344,14 @@ jint QtAndroidPrivate::initJNI(JavaVM *vm, JNIEnv *env)
}
static const JNINativeMethod methods[] = {
+ {"runPendingCppRunnables", "()V", reinterpret_cast<void *>(runPendingCppRunnables)},
{"dispatchGenericMotionEvent", "(Landroid/view/MotionEvent;)Z", reinterpret_cast<void *>(dispatchGenericMotionEvent)},
{"dispatchKeyEvent", "(Landroid/view/KeyEvent;)Z", reinterpret_cast<void *>(dispatchKeyEvent)},
+ {"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);
- env->DeleteLocalRef(jQtNative);
+
if (!regOk && QJniEnvironment::checkAndClearExceptions(env))
return JNI_ERR;
@@ -284,9 +361,17 @@ jint QtAndroidPrivate::initJNI(JavaVM *vm, JNIEnv *env)
if (!registerNativeInterfaceNatives())
return JNI_ERR;
+ g_runPendingCppRunnablesMethodID = env->GetStaticMethodID(jQtNative,
+ "runPendingCppRunnablesOnAndroidThread",
+ "()V");
+ g_jNativeClass = static_cast<jclass>(env->NewGlobalRef(jQtNative));
+ env->DeleteLocalRef(jQtNative);
+
+ qRegisterMetaType<QtAndroidPrivate::PermissionsHash>();
return JNI_OK;
}
+
jobject QtAndroidPrivate::activity()
{
return g_jActivity;
@@ -325,6 +410,110 @@ jint QtAndroidPrivate::androidSdkVersion()
return sdkVersion;
}
+void QtAndroidPrivate::runOnAndroidThread(const QtAndroidPrivate::Runnable &runnable, JNIEnv *env)
+{
+ QMutexLocker locker(&g_pendingRunnablesMutex);
+ const bool triggerRun = g_pendingRunnables->empty();
+ g_pendingRunnables->push_back(runnable);
+ locker.unlock();
+ if (triggerRun)
+ env->CallStaticVoidMethod(g_jNativeClass, g_runPendingCppRunnablesMethodID);
+}
+
+static bool waitForSemaphore(int timeoutMs, QSharedPointer<QSemaphore> sem)
+{
+ while (timeoutMs > 0) {
+ if (sem->tryAcquire(1, 10))
+ return true;
+ timeoutMs -= 10;
+ QCoreApplication::processEvents();
+ }
+ return false;
+}
+
+void QtAndroidPrivate::runOnAndroidThreadSync(const QtAndroidPrivate::Runnable &runnable, JNIEnv *env, int timeoutMs)
+{
+ QSharedPointer<QSemaphore> sem(new QSemaphore);
+ runOnAndroidThread([&runnable, sem]{
+ runnable();
+ sem->release();
+ }, env);
+ waitForSemaphore(timeoutMs, sem);
+}
+
+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 = nextRequestCode();
+ if (!directCall) {
+ QMutexLocker locker(&g_pendingPermissionRequestsMutex);
+ (*g_pendingPermissionRequests)[requestCode] = new PermissionsResultClass(callbackFunc);
+ }
+
+ runOnAndroidThread([permissions, callbackFunc, requestCode, directCall] {
+ if (directCall) {
+ QMutexLocker locker(&g_pendingPermissionRequestsMutex);
+ (*g_pendingPermissionRequests)[requestCode] = new PermissionsResultClass(callbackFunc);
+ }
+
+ QJniEnvironment env;
+ jclass clazz = env->FindClass("java/lang/String");
+
+ if (env.checkAndClearExceptions())
+ return;
+
+ auto array = env->NewObjectArray(permissions.size(), clazz, nullptr);
+ int index = 0;
+ for (const auto &perm : permissions)
+ env->SetObjectArrayElement(array, index++, QJniObject::fromString(perm).object());
+ QJniObject(activity()).callMethod<void>("requestPermissions", "([Ljava/lang/String;I)V", array, requestCode);
+ env->DeleteLocalRef(array);
+ }, env);
+}
+
+QtAndroidPrivate::PermissionsHash 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);
+ if (waitForSemaphore(timeoutMs, sem))
+ return std::move(*res);
+ else // mustn't touch *res
+ return QHash<QString, QtAndroidPrivate::PermissionsResult>();
+}
+
+QtAndroidPrivate::PermissionsResult QtAndroidPrivate::checkPermission(const QString &permission)
+{
+ const auto res = QJniObject::callStaticMethod<jint>("org/qtproject/qt/android/QtNative",
+ "checkSelfPermission",
+ "(Ljava/lang/String;)I",
+ QJniObject::fromString(permission).object());
+ return res == PERMISSION_GRANTED ? PermissionsResult::Granted : PermissionsResult::Denied;
+}
+
+bool QtAndroidPrivate::shouldShowRequestPermissionRationale(const QString &permission)
+{
+ if (androidSdkVersion() < 23 || !activity())
+ return false;
+
+ return QJniObject(activity()).callMethod<jboolean>("shouldShowRequestPermissionRationale",
+ "(Ljava/lang/String;)Z",
+ QJniObject::fromString(permission).object());
+}
+
void QtAndroidPrivate::registerGenericMotionEventListener(QtAndroidPrivate::GenericMotionEventListener *listener)
{
QMutexLocker locker(&g_genericMotionEventListeners()->mutex);
@@ -349,6 +538,13 @@ void QtAndroidPrivate::unregisterKeyEventListener(QtAndroidPrivate::KeyEventList
g_keyEventListeners()->listeners.removeOne(listener);
}
+void QtAndroidPrivate::hideSplashScreen(JNIEnv *env, int duration)
+{
+ Q_UNUSED(env)
+ QJniObject::callStaticMethod<void>("org/qtproject/qt/android/QtNative",
+ "hideSplashScreen", "(I)V", duration);
+}
+
void QtAndroidPrivate::waitForServiceSetup()
{
g_waitForServiceSetupSemaphore->acquire();
@@ -412,3 +608,5 @@ Q_CORE_EXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved)
return JNI_VERSION_1_6;
}
+
+#include "qjnihelpers.moc"