diff options
Diffstat (limited to 'src/corelib/kernel/qjnihelpers.cpp')
-rw-r--r-- | src/corelib/kernel/qjnihelpers.cpp | 204 |
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" |