Revision control
Copy as Markdown
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
// Qt-Security score:significant reason:default
#include <QtCore/private/qandroidtypeconverter_p.h>
#include <QtCore/private/qandroidtypes_p.h>
#include <QtQuick/private/qandroidquickviewembedding_p.h>
#include <QtQuick/private/qandroidviewsignalmanager_p.h>
#include <QtCore/private/qmetaobject_p.h>
#include <QtCore/qmetatype.h>
#include <QtCore/qcoreapplication.h>
#include <QtCore/qjnienvironment.h>
#include <QtCore/qjniobject.h>
#include <QtCore/qjniarray.h>
#include <QtCore/qjnitypes.h>
#include <QtQml/qqmlengine.h>
#include <QtQuick/qquickitem.h>
#include <functional>
#include <jni.h>
#include <memory>
QT_BEGIN_NAMESPACE
Q_DECLARE_JNI_CLASS(QtDelegate, "org/qtproject/qt/android/QtEmbeddedContextDelegate");
Q_DECLARE_JNI_CLASS(QtQuickView, "org/qtproject/qt/android/QtQuickView");
Q_DECLARE_JNI_CLASS(QtWindow, "org/qtproject/qt/android/QtWindow");
Q_DECLARE_JNI_CLASS(View, "android/view/View");
namespace QtAndroidQuickViewEmbedding
{
constexpr const char *uninitializedViewMessage = "because QtQuickView is not loaded or ready yet.";
static void onQQuickViewStatusChanged(const QJniObject &qtViewObject,
QAndroidQuickView::Status status)
{
auto future = QNativeInterface::QAndroidApplication::runOnAndroidMainThread(
[qtViewObject, status] {
qtViewObject.callMethod<void>("handleStatusChange", status);
}, QDeadlineTimer(1000));
future.waitForFinished(); // Wait for the user to handle status change.
}
void createQuickView(JNIEnv *, jobject nativeWindow, jstring qmlUri, jint width, jint height,
jlong parentWindowReference, jlong viewReference,
const QJniArray<jstring> &qmlImportPaths)
{
static_assert (sizeof(jlong) >= sizeof(void*),
"Insufficient size of Java type to hold the c++ pointer");
const QUrl qmlUrl(QJniObject(qmlUri).toString());
const QStringList importPaths = qmlImportPaths.toContainer();
QMetaObject::invokeMethod(qApp, [qtViewObject = QJniObject(nativeWindow),
parentWindowReference,
viewReference,
width,
height,
qmlUrl,
importPaths] {
// If the view does not exists (viewReference==0) we should create and set it up.
// Else we only reset the source of the view.
QAndroidQuickView *view = reinterpret_cast<QAndroidQuickView *>(viewReference);
if (!view) {
QWindow *parentWindow = reinterpret_cast<QWindow *>(parentWindowReference);
view = new QAndroidQuickView(parentWindow);
QObject::connect(
view, &QAndroidQuickView::statusChanged, view,
std::bind(&onQQuickViewStatusChanged, qtViewObject, std::placeholders::_1));
view->setResizeMode(QAndroidQuickView::SizeRootObjectToView);
view->setColor(QColor(Qt::transparent));
view->setWidth(width);
view->setHeight(height);
QQmlEngine *engine = view->engine();
for (const QString &path : importPaths)
engine->addImportPath(path);
QObject::connect(engine, &QQmlEngine::quit, QCoreApplication::instance(),
&QCoreApplication::quit);
const QtJniTypes::QtWindow window = reinterpret_cast<jobject>(view->winId());
qtViewObject.callMethod<void>("addQtWindow",
window,
reinterpret_cast<jlong>(view),
parentWindowReference);
}
view->setSource(qmlUrl);
});
}
std::pair<QAndroidQuickView *, QQuickItem *> getViewAndRootObject(jlong windowReference)
{
QAndroidQuickView *view = reinterpret_cast<QAndroidQuickView *>(windowReference);
QQuickItem *rootObject = view ? view->rootObject() : nullptr;
return std::make_pair(view, rootObject);
}
void setRootObjectProperty(JNIEnv *env, jobject object, jlong windowReference,
jstring propertyName, jobject value)
{
Q_UNUSED(env);
Q_UNUSED(object);
auto [_, rootObject] = getViewAndRootObject(windowReference);
if (!rootObject) {
qWarning("Cannot set property %s %s", qPrintable(QJniObject(propertyName).toString()),
uninitializedViewMessage);
return;
}
const QString property = QJniObject(propertyName).toString();
const QMetaObject *rootMetaObject = rootObject->metaObject();
int propertyIndex = rootMetaObject->indexOfProperty(qPrintable(property));
if (propertyIndex < 0) {
qWarning("Property %s does not exist in the root QML object.", qPrintable(property));
return;
}
const QMetaProperty metaProperty = rootMetaObject->property(propertyIndex);
if (!metaProperty.isWritable()) {
qWarning("Property %s of the root QML object is not writable.",
qPrintable(property));
return;
}
const QJniObject propertyValue(value);
const QVariant variantToWrite = QAndroidTypeConverter::toQVariant(propertyValue);
if (!variantToWrite.isValid()) {
qWarning("Setting the property type of %s is not supported.",
propertyValue.className().data());
} else {
QMetaObject::invokeMethod(rootObject,
[metaProperty, rootObject = rootObject, variantToWrite, property] {
if (!metaProperty.write(rootObject, variantToWrite)) {
qWarning("Failed to write property %s of the root QML object.",
qPrintable(property));
}
});
}
}
jobject getRootObjectProperty(JNIEnv *env, jobject object, jlong windowReference,
jstring propertyName)
{
Q_UNUSED(object);
Q_ASSERT(env);
const QString property = QJniObject(propertyName).toString();
auto [_, rootObject] = getViewAndRootObject(windowReference);
if (!rootObject) {
qWarning("Cannot get property %s %s", qPrintable(property), uninitializedViewMessage);
return nullptr;
}
const QMetaObject *rootMetaObject = rootObject->metaObject();
int propertyIndex = rootMetaObject->indexOfProperty(property.toUtf8().constData());
if (propertyIndex < 0) {
qWarning("Cannot get property %s as it does not exist in the root QML object.",
qPrintable(property));
return nullptr;
}
QMetaProperty metaProperty = rootMetaObject->property(propertyIndex);
QVariant propertyValue;
if (QCoreApplication::instance()->thread()->isCurrentThread()) {
propertyValue = metaProperty.read(rootObject);
} else {
QMetaObject::invokeMethod(rootObject,
[&propertyValue, &metaProperty, rootObject = rootObject] {
propertyValue = metaProperty.read(rootObject);
}, Qt::BlockingQueuedConnection);
}
jobject jObject = QAndroidTypeConverter::toJavaObject(propertyValue, env);
if (!jObject) {
qWarning("Property %s cannot be converted to a supported Java data type.",
qPrintable(property));
}
return jObject;
}
bool addRootObjectSignalListener(JNIEnv *env, jobject, jlong windowReference,
jstring signalName, QJniArray<jclass> argTypes,
jobject listener, jint id)
{
Q_ASSERT(env);
auto [view, _] = getViewAndRootObject(windowReference);
if (!view) {
qWarning("Cannot connect to signal %s %s",
qPrintable(QJniObject(signalName).toString()), uninitializedViewMessage);
return false;
}
QAndroidViewSignalManager *signalManager = view->signalManager();
return signalManager->addConnection(QJniObject(signalName).toString(), argTypes,
QJniObject(listener), id);
}
bool removeRootObjectSignalListener(JNIEnv *, jobject, jlong windowReference,
jint signalListenerId)
{
auto [view, rootObject] = getViewAndRootObject(windowReference);
if (!rootObject) {
qWarning("Cannot disconnect the signal connection with id: %i %s", signalListenerId,
uninitializedViewMessage);
return false;
}
view->signalManager()->removeConnection(signalListenerId);
return true;
}
QVariant jobjectToVariant(QMetaType::Type type, jobject &obj)
{
switch (type) {
case QMetaType::Bool:
return QVariant::fromValue(
QtJniTypes::Boolean::construct(obj).callMethod<bool>("booleanValue"));
break;
case QMetaType::Int:
return QVariant::fromValue(
QtJniTypes::Integer::construct(obj).callMethod<int>("intValue"));
break;
case QMetaType::Double:
return QVariant::fromValue(
QtJniTypes::Double::construct(obj).callMethod<double>("doubleValue"));
break;
case QMetaType::Float:
return QVariant::fromValue(
QtJniTypes::Float::construct(obj).callMethod<float>("floatValue"));
break;
case QMetaType::QString:
return QVariant::fromValue(QJniObject(obj).toString());
break;
default:
qWarning("Unsupported metatype: %s", QMetaType(type).name());
return QVariant();
}
}
QMetaMethod findMethod(const QString &name, int paramCount, const QMetaObject &object)
{
for (auto i = object.methodOffset(); i < object.methodCount(); ++i) {
QMetaMethod method = object.method(i);
const auto paramMatch = method.parameterCount() == paramCount;
const auto nameMatch = method.name() == name.toUtf8();
if (paramMatch && nameMatch)
return method;
}
return QMetaMethod();
}
void invokeMethod(JNIEnv *, jobject, jlong viewReference, QtJniTypes::String methodName,
QJniArray<jobject> jniParams)
{
auto [_, rootObject] = getViewAndRootObject(viewReference);
if (!rootObject) {
qWarning() << "Cannot invoke QML method" << methodName.toString()
<< "as the QML view has not been loaded yet.";
return;
}
const auto paramCount = jniParams.size();
QMetaMethod method =
findMethod(methodName.toString(), paramCount, *rootObject->metaObject());
if (!method.isValid()) {
qWarning() << "Failed to find method" << QJniObject(methodName).toString()
<< "in QQuickView";
return;
}
// Invoke and leave early if there are no params to pass on
if (paramCount == 0) {
method.invoke(rootObject, Qt::QueuedConnection);
return;
}
QList<QVariant> variants;
variants.reserve(jniParams.size());
variants.emplace_back(QVariant{}); // "Data" for the return value
for (auto i = 0; i < paramCount; ++i) {
const auto type = method.parameterType(i);
if (type == QMetaType::UnknownType) {
qWarning("Unknown metatypes are not supported.");
return;
}
jobject rawParam = jniParams.at(i);
auto variant = variants.emplace_back(
jobjectToVariant(static_cast<QMetaType::Type>(type), rawParam));
if (variant.isNull()) {
auto className = QJniObject(rawParam).className();
qWarning("Failed to convert param with class name '%s' to QVariant",
className.constData());
return;
}
}
// Initialize the data arrays for params, typenames and type conversion interfaces.
// Note that this is adding an element, this is for the return value which is at idx 0.
const int paramsCount = method.parameterCount() + 1;
const auto paramTypes = std::make_unique<const char *[]>(paramsCount);
const auto params = std::make_unique<const void *[]>(paramsCount);
const auto metaTypes =
std::make_unique<const QtPrivate::QMetaTypeInterface *[]>(paramsCount);
// We're not expecting a return value, so index 0 can be all nulls.
paramTypes[0] = nullptr;
params[0] = nullptr;
metaTypes[0] = nullptr;
for (auto i = 1; i < variants.size(); ++i) {
const auto &variant = variants.at(i);
paramTypes[i] = variant.typeName();
params[i] = variant.data();
metaTypes[i] = variant.metaType().iface();
}
auto reason = QMetaMethodInvoker::invokeImpl(method,
rootObject,
Qt::QueuedConnection,
paramsCount,
params.get(),
paramTypes.get(),
metaTypes.get());
if (reason != QMetaMethodInvoker::InvokeFailReason::None)
qWarning() << "Failed to invoke function" << methodName.toString()
<< ", Reason:" << int(reason);
}
bool registerNatives(QJniEnvironment& env) {
return env.registerNativeMethods(QtJniTypes::Traits<QtJniTypes::QtQuickView>::className(),
{Q_JNI_NATIVE_SCOPED_METHOD(createQuickView,
QtAndroidQuickViewEmbedding),
Q_JNI_NATIVE_SCOPED_METHOD(setRootObjectProperty,
QtAndroidQuickViewEmbedding),
Q_JNI_NATIVE_SCOPED_METHOD(getRootObjectProperty,
QtAndroidQuickViewEmbedding),
Q_JNI_NATIVE_SCOPED_METHOD(addRootObjectSignalListener,
QtAndroidQuickViewEmbedding),
Q_JNI_NATIVE_SCOPED_METHOD(removeRootObjectSignalListener,
QtAndroidQuickViewEmbedding),
Q_JNI_NATIVE_SCOPED_METHOD(invokeMethod,
QtAndroidQuickViewEmbedding)});
}
}
extern "C" Q_DECL_EXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved)
{
Q_UNUSED(vm)
Q_UNUSED(reserved)
static bool initialized = false;
if (initialized)
return JNI_VERSION_1_6;
initialized = true;
QJniEnvironment env;
if (!env.isValid())
return JNI_ERR;
if (!QtAndroidQuickViewEmbedding::registerNatives(env))
return JNI_ERR;
return JNI_VERSION_1_6;
}
QT_END_NAMESPACE