diff options
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); |