373 lines
11 KiB
C++
373 lines
11 KiB
C++
// Author: Ben Lau (https://github.com/benlau)
|
|
#include <QCoreApplication>
|
|
#include <QPointer>
|
|
#include <QtCore>
|
|
#include <QPair>
|
|
#include <QQueue>
|
|
#include "systemdispatcher.h"
|
|
|
|
using namespace AndroidNative;
|
|
|
|
static QPointer<SystemDispatcher> m_instance;
|
|
|
|
QString SystemDispatcher::ACTIVITY_RESUME_MESSAGE = "Activity.onResume";
|
|
QString SystemDispatcher::ACTIVITY_RESULT_MESSAGE = "Activity.onActivityResult";
|
|
static bool registerNativesCalled = false;
|
|
|
|
#ifdef Q_OS_ANDROID
|
|
#include <QAndroidJniObject>
|
|
#include <QAndroidJniEnvironment>
|
|
|
|
#define JCLASS_Name "androidnative/SystemDispatcher"
|
|
#define DISPATCH_SIGNATURE "(Ljava/lang/String;Ljava/util/Map;)V"
|
|
#define EMIT_SIGNATURE "(Ljava/lang/String;Ljava/util/Map;)V"
|
|
|
|
static QVariantMap createVariantMap(jobject data);
|
|
static jobject createHashMap(const QVariantMap &data);
|
|
|
|
static QVariant convertToQVariant(QAndroidJniObject value) {
|
|
QVariant v;
|
|
if (!value.isValid()) {
|
|
return v;
|
|
}
|
|
|
|
QAndroidJniEnvironment env;
|
|
|
|
jclass jclass_of_string = env->FindClass("java/lang/String");
|
|
jclass jclass_of_integer = env->FindClass("java/lang/Integer");
|
|
jclass jclass_of_boolean = env->FindClass("java/lang/Boolean");
|
|
jclass jclass_of_list = env->FindClass("java/util/List");
|
|
jclass jclass_of_map = env->FindClass("java/util/Map");
|
|
|
|
if (env->IsInstanceOf(value.object<jobject>(),jclass_of_boolean)) {
|
|
v = QVariant::fromValue<bool>(value.callMethod<jboolean>("booleanValue","()Z"));
|
|
} else if (env->IsInstanceOf(value.object<jobject>(),jclass_of_integer)) {
|
|
v = value.callMethod<jint>("intValue","()I");
|
|
} else if (env->IsInstanceOf(value.object<jobject>(),jclass_of_string)) {
|
|
v = value.toString();
|
|
} else if (env->IsInstanceOf(value.object<jobject>(), jclass_of_map)) {
|
|
v = createVariantMap(value.object<jobject>());
|
|
} else if (env->IsInstanceOf(value.object<jobject>(),jclass_of_list)) {
|
|
QVariantList list;
|
|
int count = value.callMethod<jint>("size","()I");
|
|
for (int i = 0 ; i < count ; i++) {
|
|
QAndroidJniObject item = value.callObjectMethod("get","(I)Ljava/lang/Object;",i);
|
|
list.append(convertToQVariant(item));
|
|
}
|
|
v = list;
|
|
}
|
|
|
|
env->DeleteLocalRef(jclass_of_string);
|
|
env->DeleteLocalRef(jclass_of_integer);
|
|
env->DeleteLocalRef(jclass_of_boolean);
|
|
env->DeleteLocalRef(jclass_of_list);
|
|
env->DeleteLocalRef(jclass_of_map);
|
|
|
|
return v;
|
|
}
|
|
|
|
static QVariantMap createVariantMap(jobject data) {
|
|
QVariantMap res;
|
|
|
|
QAndroidJniEnvironment env;
|
|
/* Reference : https://community.oracle.com/thread/1549999 */
|
|
|
|
// Get the HashMap Class
|
|
jclass jclass_of_hashmap = (env)->GetObjectClass(data);
|
|
|
|
// Get link to Method "entrySet"
|
|
jmethodID entrySetMethod = (env)->GetMethodID(jclass_of_hashmap, "entrySet", "()Ljava/util/Set;");
|
|
|
|
// Invoke the "entrySet" method on the HashMap object
|
|
jobject jobject_of_entryset = env->CallObjectMethod(data, entrySetMethod);
|
|
|
|
// Get the Set Class
|
|
jclass jclass_of_set = (env)->FindClass("java/util/Set"); // Problem during compilation !!!!!
|
|
|
|
if (jclass_of_set == 0) {
|
|
qWarning() << "java/util/Set lookup failed\n";
|
|
return res;
|
|
}
|
|
|
|
// Get link to Method "iterator"
|
|
jmethodID iteratorMethod = env->GetMethodID(jclass_of_set, "iterator", "()Ljava/util/Iterator;");
|
|
|
|
// Invoke the "iterator" method on the jobject_of_entryset variable of type Set
|
|
jobject jobject_of_iterator = env->CallObjectMethod(jobject_of_entryset, iteratorMethod);
|
|
|
|
// Get the "Iterator" class
|
|
jclass jclass_of_iterator = (env)->FindClass("java/util/Iterator");
|
|
|
|
// Get link to Method "hasNext"
|
|
jmethodID hasNextMethod = env->GetMethodID(jclass_of_iterator, "hasNext", "()Z");
|
|
|
|
jmethodID nextMethod = env->GetMethodID(jclass_of_iterator, "next", "()Ljava/lang/Object;");
|
|
|
|
while (env->CallBooleanMethod(jobject_of_iterator, hasNextMethod) ) {
|
|
jobject jEntry = env->CallObjectMethod(jobject_of_iterator,nextMethod);
|
|
QAndroidJniObject entry = QAndroidJniObject(jEntry);
|
|
QAndroidJniObject key = entry.callObjectMethod("getKey","()Ljava/lang/Object;");
|
|
QAndroidJniObject value = entry.callObjectMethod("getValue","()Ljava/lang/Object;");
|
|
QString k = key.toString();
|
|
|
|
QVariant v = convertToQVariant(value);
|
|
|
|
env->DeleteLocalRef(jEntry);
|
|
|
|
if (v.isNull()) {
|
|
continue;
|
|
}
|
|
|
|
res[k] = v;
|
|
}
|
|
|
|
if (env->ExceptionOccurred()) {
|
|
env->ExceptionDescribe();
|
|
env->ExceptionClear();
|
|
}
|
|
|
|
env->DeleteLocalRef(jclass_of_hashmap);
|
|
env->DeleteLocalRef(jobject_of_entryset);
|
|
env->DeleteLocalRef(jclass_of_set);
|
|
env->DeleteLocalRef(jobject_of_iterator);
|
|
env->DeleteLocalRef(jclass_of_iterator);
|
|
|
|
return res;
|
|
}
|
|
|
|
static jobject convertToJObject(QVariant v) {
|
|
jobject res = 0;
|
|
QAndroidJniEnvironment env;
|
|
|
|
if (v.type() == QVariant::String) {
|
|
QString str = v.toString();
|
|
res = env->NewStringUTF(str.toLocal8Bit().data());
|
|
} else if (v.type() == QVariant::Int) {
|
|
jclass integerClass = env->FindClass("java/lang/Integer");
|
|
jmethodID integerConstructor = env->GetMethodID(integerClass, "<init>", "(I)V");
|
|
|
|
res = env->NewObject(integerClass,integerConstructor,v.toInt());
|
|
|
|
env->DeleteLocalRef(integerClass);
|
|
} else if (v.type() == QVariant::Bool) {
|
|
jclass booleanClass = env->FindClass("java/lang/Boolean");
|
|
jmethodID booleanConstructor = env->GetMethodID(booleanClass,"<init>","(Z)V");
|
|
|
|
res = env->NewObject(booleanClass,booleanConstructor,v.toBool());
|
|
|
|
env->DeleteLocalRef(booleanClass);
|
|
|
|
} else if (v.type() == QVariant::Map) {
|
|
res = createHashMap(v.toMap());
|
|
} else if (v.type() == QVariant::List){
|
|
QVariantList list = v.value<QVariantList>();
|
|
jclass arrayListClass = env->FindClass("java/util/ArrayList");
|
|
jmethodID init = env->GetMethodID(arrayListClass, "<init>", "(I)V");
|
|
res = env->NewObject( arrayListClass, init, list.size());
|
|
|
|
jmethodID add = env->GetMethodID( arrayListClass, "add",
|
|
"(Ljava/lang/Object;)Z");
|
|
|
|
for (int i = 0 ; i < list.size() ; i++) {
|
|
jobject item = convertToJObject(list.at(i));
|
|
env->CallBooleanMethod(res,add, item);
|
|
env->DeleteLocalRef(item);
|
|
}
|
|
|
|
env->DeleteLocalRef(arrayListClass);
|
|
} else {
|
|
qWarning() << "ANSystemDispatcher: Non-supported data type - " << v.type();
|
|
}
|
|
return res;
|
|
}
|
|
|
|
static jobject createHashMap(const QVariantMap &data) {
|
|
QAndroidJniEnvironment env;
|
|
|
|
jclass mapClass = env->FindClass("java/util/HashMap");
|
|
|
|
if (mapClass == NULL) {
|
|
qWarning() << "Failed to find class" << "java/util/HashMap";
|
|
return NULL;
|
|
}
|
|
|
|
jsize map_len = data.size();
|
|
|
|
jmethodID init = env->GetMethodID(mapClass, "<init>", "(I)V");
|
|
jobject hashMap = env->NewObject( mapClass, init, map_len);
|
|
|
|
jmethodID put = env->GetMethodID( mapClass, "put",
|
|
"(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;");
|
|
|
|
QMapIterator<QString, QVariant> iter(data);
|
|
while (iter.hasNext()) {
|
|
iter.next();
|
|
|
|
QString key = iter.key();
|
|
jstring jkey = env->NewStringUTF(key.toLocal8Bit().data());
|
|
QVariant v = iter.value();
|
|
jobject item = convertToJObject(v);
|
|
|
|
if (item == 0) {
|
|
continue;
|
|
}
|
|
|
|
env->CallObjectMethod(hashMap,put,jkey,item);
|
|
env->DeleteLocalRef(item);
|
|
env->DeleteLocalRef(jkey);
|
|
}
|
|
|
|
if (env->ExceptionOccurred()) {
|
|
env->ExceptionDescribe();
|
|
env->ExceptionClear();
|
|
}
|
|
|
|
env->DeleteLocalRef(mapClass);
|
|
|
|
return hashMap;
|
|
}
|
|
|
|
static void jniEmit(JNIEnv* env,jobject object,jstring name,jobject data) {
|
|
Q_UNUSED(object);
|
|
Q_UNUSED(env);
|
|
|
|
QAndroidJniObject tmp(name);
|
|
QString str = tmp.toString();
|
|
|
|
QVariantMap map;
|
|
|
|
if (data != 0) {
|
|
map = createVariantMap(data);
|
|
}
|
|
|
|
if (m_instance.isNull()) {
|
|
return;
|
|
}
|
|
|
|
QMetaObject::invokeMethod(m_instance.data(),"dispatched",Qt::AutoConnection,
|
|
Q_ARG(QString, str),
|
|
Q_ARG(QVariantMap,map));
|
|
}
|
|
|
|
static void printInstruction() {
|
|
static bool printOnce = false;
|
|
|
|
if (printOnce) {
|
|
return;
|
|
}
|
|
printOnce = true;
|
|
|
|
// BuildMyString.com generated code. Please enjoy your string responsibly.
|
|
|
|
QString msg = "Using SystemDispatcher in Android but SystemDispatcher::registerNatives() is never called. Instruction: \r\n"
|
|
"https://github.com/benlau/androidnative.pri/blob/master/docs/installation.md\r\n";
|
|
|
|
qWarning() << msg;
|
|
}
|
|
#endif
|
|
|
|
AndroidNative::SystemDispatcher::SystemDispatcher(QObject* parent) : QObject(parent)
|
|
{
|
|
}
|
|
|
|
AndroidNative::SystemDispatcher::~SystemDispatcher()
|
|
{
|
|
}
|
|
|
|
AndroidNative::SystemDispatcher *SystemDispatcher::instance()
|
|
{
|
|
if (!m_instance) {
|
|
QCoreApplication* app = QCoreApplication::instance();
|
|
m_instance = new SystemDispatcher(app);
|
|
}
|
|
return m_instance;
|
|
}
|
|
|
|
void AndroidNative::SystemDispatcher::dispatch(QString type, QVariantMap message)
|
|
{
|
|
Q_UNUSED(type);
|
|
Q_UNUSED(message);
|
|
|
|
#ifdef Q_OS_ANDROID
|
|
if (!registerNativesCalled) {
|
|
printInstruction();
|
|
}
|
|
|
|
QAndroidJniEnvironment env;
|
|
|
|
jstring jType = env->NewStringUTF(type.toLocal8Bit().data());
|
|
jobject jData = createHashMap(message);
|
|
QAndroidJniObject::callStaticMethod<void>(JCLASS_Name, "dispatch",
|
|
DISPATCH_SIGNATURE,
|
|
jType,jData);
|
|
env->DeleteLocalRef(jType);
|
|
env->DeleteLocalRef(jData);
|
|
|
|
#else
|
|
static bool dispatching = false;
|
|
static QQueue<QPair<QString,QVariantMap> > queue;
|
|
|
|
|
|
if (dispatching) {
|
|
queue.enqueue(QPair<QString,QVariantMap> (type,message) );
|
|
return;
|
|
}
|
|
|
|
dispatching = true;
|
|
emit dispatched(type,message);
|
|
|
|
while (queue.size() > 0) {
|
|
QPair<QString,QVariantMap> pair = queue.dequeue();
|
|
emit dispatched(pair.first,pair.second);
|
|
}
|
|
dispatching = false;
|
|
#endif
|
|
}
|
|
|
|
void AndroidNative::SystemDispatcher::loadClass(QString javaClassName)
|
|
{
|
|
QVariantMap message;
|
|
message["className"] = javaClassName;
|
|
|
|
dispatch("androidnative.SystemDispatcher.loadClass",message);
|
|
}
|
|
|
|
void AndroidNative::SystemDispatcher::registerNatives()
|
|
{
|
|
Q_UNUSED(registerNativesCalled);
|
|
|
|
#ifdef Q_OS_ANDROID
|
|
registerNativesCalled = true;
|
|
|
|
QAndroidJniEnvironment env;
|
|
jclass clazz = env->FindClass(JCLASS_Name);
|
|
if (!clazz)
|
|
{
|
|
qCritical() << "androidNative.SystemDispatcher Java class is not found. Please configure your build.gradle according to this instruction: \r\n"
|
|
"https://github.com/benlau/androidnative.pri/blob/master/docs/installation.md\r\n";
|
|
if (env->ExceptionOccurred()) {
|
|
env->ExceptionDescribe();
|
|
env->ExceptionClear();
|
|
}
|
|
return;
|
|
}
|
|
|
|
JNINativeMethod methods[] =
|
|
{
|
|
{"jniEmit", EMIT_SIGNATURE, (void *)&jniEmit},
|
|
};
|
|
|
|
int numMethods = sizeof(methods) / sizeof(methods[0]);
|
|
if (env->RegisterNatives(clazz, methods, numMethods) < 0) {
|
|
if (env->ExceptionOccurred()) {
|
|
env->ExceptionDescribe();
|
|
env->ExceptionClear();
|
|
qCritical() << "Exception occurred!!!";
|
|
return;
|
|
}
|
|
}
|
|
|
|
QAndroidJniObject::callStaticMethod<void>(JCLASS_Name, "init",
|
|
"()V");
|
|
#endif
|
|
}
|