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>
bb10
BogDan Vatra 2016-09-07 16:55:51 +03:00
parent fdca8cb09e
commit 31a63b6903
6 changed files with 158 additions and 7 deletions

View File

@ -1455,4 +1455,9 @@ public class QtActivityDelegate
}
return false;
}
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults)
{
QtNative.sendRequestPermissionsResult(requestCode, permissions, grantResults);
}
}

View File

@ -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);
}

View File

@ -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;
}
}
}

View File

@ -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)
{

View File

@ -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"

View File

@ -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);