// 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include 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("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 &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(viewReference); if (!view) { QWindow *parentWindow = reinterpret_cast(parentWindowReference); view = new QAndroidQuickView(parentWindow); QObject::connect( view, &QAndroidQuickView::statusChanged, 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(view->winId()); qtViewObject.callMethod("addQtWindow", window, reinterpret_cast(view), parentWindowReference); } view->setSource(qmlUrl); }); } std::pair getViewAndRootObject(jlong windowReference) { QAndroidQuickView *view = reinterpret_cast(windowReference); QQuickItem *rootObject = Q_LIKELY(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 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 = rootMetaObject->property(propertyIndex), rootObject = rootObject, variantToWrite] { metaProperty.write(rootObject, variantToWrite); }); } } 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 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("booleanValue")); break; case QMetaType::Int: return QVariant::fromValue( QtJniTypes::Integer::construct(obj).callMethod("intValue")); break; case QMetaType::Double: return QVariant::fromValue( QtJniTypes::Double::construct(obj).callMethod("doubleValue")); break; case QMetaType::Float: return QVariant::fromValue( QtJniTypes::Float::construct(obj).callMethod("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 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 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(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(paramsCount); const auto params = std::make_unique(paramsCount); const auto metaTypes = std::make_unique(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::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)}); } } 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