// Copyright (C) 2024 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 #ifndef ANDROIDBACKENDREGISTER_H #define ANDROIDBACKENDREGISTER_H #include #include #include #include #include #include #include #include QT_BEGIN_NAMESPACE Q_DECLARE_LOGGING_CATEGORY(lcAndroidBackendRegister) /* \internal This class is used to [un]register QJniObjects which implement specific interfaces. These objects can then be fetched or directly called by using that interface. This is intended to decouple the Qt C++ code from the underlying Java implementation, as Qt now has multiple separate usecases, each of which may have different implementations and support different features. To use this register, the interface must be declared as a JNI class via Q_DECLARE_JNI_CLASS: Q_DECLARE_JNI_CLASS(ImaginaryInterface, "org/qtproject/qt/android/ImaginaryInterface") Where ImaginaryInterface is a Java interface like this: @UsedFromNativeCode interface ImaginaryInterface { void doSomething(int imaginary, int imaginary2); } After that, the features provided by that interface can be used in the C++ code in two ways: Use the convenience method callInterface() to call a method directly: AndroidBackendRegister *reg = QtAndroid::backendRegister(); int imaginary, imaginary2; reg->callInterface("doSomething", imaginary, imaginary2); Or get the QJniObject directly and use it as you would any other QJniObject: AndroidBackendRegister *reg = QtAndroid::backendRegister(); auto imaginary = reg->getInterface(); // ... do whatever with QJniObject In order to register a new interface on the Java side, the BackendRegister class must be used, with its native functions registerBackend() and unregisterBackend(): BackendRegister.registerBackend(ImaginaryInterface.class, imaginaryInterfaceObject); and BackendRegister.unregisterBackend(ImaginaryInterface.class); Note that only one object can be registered for each interface. If multiple objects are registered, only the latest one is kept. Thus, you only need to declare the interface you want to unregister, not the object that implements the interface as well. If the interface needs to be available as soon as possible, it should be registered immediately after Qt has started, by using the QtNative app state listener functionality. */ template using ValidInterfaceType = std::enable_if_t, bool>; class AndroidBackendRegister { public: static bool registerNatives(); /* \internal Returns a QJniObject which is registered for the given interface. Requires the type of the requested interface to be registered via Q_DECLARE_JNI_CLASS. (see ValidInterfaceType). */ template = true> [[nodiscard]] T getInterface() { QMutexLocker lock(&m_registerMutex); return m_register.value(QString(QtJniTypes::Traits::className().data())); } template using IsObjectType = typename std::disjunction, std::is_base_of>; /* \internal Convenience function that calls getInterface() and then QJniObject::callMethod() on the resulting object, forwarding the rest of the parameters to that function. If the interface is not registered, a warning is printed and an empty object is returned. */ template = true> auto callInterface(const char *func, Args... args) { if (const auto obj = getInterface(); obj.isValid()) { return obj.template callMethod(func, std::forward(args)...); } else { qWarning() << "No interface with className" << QtJniTypes::Traits::className() << "has been registered."; } if constexpr (IsObjectType::value) return Ret(QJniObject()); if constexpr (!std::is_same_v) return Ret{}; } private: QMutex m_registerMutex; QMap m_register; static jboolean isNull(JNIEnv *, jclass); Q_DECLARE_JNI_NATIVE_METHOD_IN_CURRENT_SCOPE(isNull) static void registerBackend(JNIEnv *, jclass, jclass interfaceClass, jobject interface); Q_DECLARE_JNI_NATIVE_METHOD_IN_CURRENT_SCOPE(registerBackend) static void unregisterBackend(JNIEnv *, jclass, jclass interfaceClass); Q_DECLARE_JNI_NATIVE_METHOD_IN_CURRENT_SCOPE(unregisterBackend) }; QT_END_NAMESPACE #endif // ANDROIDBACKENDREGISTER_H