From 367092d7e02bfe0054b78e856b7addbdb56aae2e Mon Sep 17 00:00:00 2001 From: Volker Hilsheimer Date: Tue, 26 Apr 2022 13:19:46 +0200 Subject: [PATCH] Return specific types for frequently used Java objects This allows us to specialize JNI type signature templates for e.g. the context object, which in Java signatures is "android/content/Context". Introduce a Q_DECLARE_JNI_TYPE macro that takes care of the plumbing. The types declared this way live in the QtJniTypes namespace, and transparently convert from and to jobject. Since jobject is a typedef to _jobject* we cannot create a subclass. Use a "Object" superclass that we can provide a QJniObject constructor for so that we don't require the QJniObject declaration to be able to use the macro. The APIs in the QNativeInterface namespace doesn't provide source or binary compatibility guarantees, so we can change the return types. Change-Id: I4cf9fa734ec9a5550b6fddeb14ef0ffd72663f29 Reviewed-by: Assam Boudjelthia --- .../kernel/qcoreapplication_platform.h | 9 ++++++ src/corelib/kernel/qjnihelpers.cpp | 6 ++-- src/corelib/kernel/qjnihelpers_p.h | 10 +++++-- src/corelib/kernel/qjniobject.h | 1 + src/corelib/kernel/qjnitypes.h | 30 ++++++++++++++++++- .../android/qandroidnativeinterface.cpp | 4 +-- .../platforms/android/androidjnimain.cpp | 8 ++--- .../platforms/android/androidjnimain.h | 5 ++-- .../kernel/qjnitypes/tst_qjnitypes.cpp | 9 ++++++ 9 files changed, 67 insertions(+), 15 deletions(-) diff --git a/src/corelib/kernel/qcoreapplication_platform.h b/src/corelib/kernel/qcoreapplication_platform.h index 9807cb78172..fe0fee915b9 100644 --- a/src/corelib/kernel/qcoreapplication_platform.h +++ b/src/corelib/kernel/qcoreapplication_platform.h @@ -18,6 +18,7 @@ #include #if defined(Q_OS_ANDROID) || defined(Q_CLANG_QDOC) +#include #if QT_CONFIG(future) && !defined(QT_NO_QOBJECT) #include #include @@ -31,13 +32,21 @@ typedef _jobject* jobject; QT_BEGIN_NAMESPACE +#if defined(Q_OS_ANDROID) +Q_DECLARE_JNI_TYPE(Context, "Landroid/content/Context;") +#endif + namespace QNativeInterface { #if defined(Q_OS_ANDROID) || defined(Q_CLANG_QDOC) struct Q_CORE_EXPORT QAndroidApplication { QT_DECLARE_NATIVE_INTERFACE(QAndroidApplication, 1, QCoreApplication) +#ifdef Q_CLANG_QDOC static jobject context(); +#else + static QtJniTypes::Context context(); +#endif static bool isActivityContext(); static int sdkVersion(); static void hideSplashScreen(int duration = 0); diff --git a/src/corelib/kernel/qjnihelpers.cpp b/src/corelib/kernel/qjnihelpers.cpp index 6954ed11930..ec6a7a58a3c 100644 --- a/src/corelib/kernel/qjnihelpers.cpp +++ b/src/corelib/kernel/qjnihelpers.cpp @@ -289,18 +289,18 @@ jint QtAndroidPrivate::initJNI(JavaVM *vm, JNIEnv *env) return JNI_OK; } -jobject QtAndroidPrivate::activity() +QtJniTypes::Activity QtAndroidPrivate::activity() { QReadLocker locker(g_updateMutex()); return g_jActivity; } -jobject QtAndroidPrivate::service() +QtJniTypes::Service QtAndroidPrivate::service() { return g_jService; } -jobject QtAndroidPrivate::context() +QtJniTypes::Context QtAndroidPrivate::context() { QReadLocker locker(g_updateMutex()); if (g_jActivity) diff --git a/src/corelib/kernel/qjnihelpers_p.h b/src/corelib/kernel/qjnihelpers_p.h index b0f80204e97..bce2b782de4 100644 --- a/src/corelib/kernel/qjnihelpers_p.h +++ b/src/corelib/kernel/qjnihelpers_p.h @@ -18,9 +18,13 @@ #include #include #include +#include QT_BEGIN_NAMESPACE +Q_DECLARE_JNI_TYPE(Activity, "Landroid/app/Activity;") +Q_DECLARE_JNI_TYPE(Service, "Landroid/app/Service;") + namespace QtAndroidPrivate { class Q_CORE_EXPORT ActivityResultListener @@ -66,9 +70,9 @@ namespace QtAndroidPrivate virtual jobject onBind(jobject intent) = 0; }; - Q_CORE_EXPORT jobject activity(); - Q_CORE_EXPORT jobject service(); - Q_CORE_EXPORT jobject context(); + Q_CORE_EXPORT QtJniTypes::Activity activity(); + Q_CORE_EXPORT QtJniTypes::Service service(); + Q_CORE_EXPORT QtJniTypes::Context context(); Q_CORE_EXPORT JavaVM *javaVM(); Q_CORE_EXPORT jint initJNI(JavaVM *vm, JNIEnv *env); Q_CORE_EXPORT jclass findClass(const char *className, JNIEnv *env); diff --git a/src/corelib/kernel/qjniobject.h b/src/corelib/kernel/qjniobject.h index 0d5c95b4052..bbaa6ee70c9 100644 --- a/src/corelib/kernel/qjniobject.h +++ b/src/corelib/kernel/qjniobject.h @@ -42,6 +42,7 @@ public: std::forward(args)...) {} QJniObject(jobject globalRef); + inline QJniObject(QtJniTypes::Object wrapper) noexcept : QJniObject(jobject(wrapper)) {} ~QJniObject(); jobject object() const; diff --git a/src/corelib/kernel/qjnitypes.h b/src/corelib/kernel/qjnitypes.h index 3d723a87825..e5306f3c545 100644 --- a/src/corelib/kernel/qjnitypes.h +++ b/src/corelib/kernel/qjnitypes.h @@ -227,7 +227,8 @@ static constexpr bool isObjectType() return true; } else { constexpr auto signature = typeSignature(); - return signature.startsWith('L') && signature.endsWith(';'); + return (signature.startsWith('L') || signature.startsWith('[')) + && signature.endsWith(';'); } } @@ -273,8 +274,35 @@ static constexpr auto constructorSignature() return methodSignature(); } +// A generic thin wrapper around jobject, convertible to jobject. +// We need this as a baseclass so that QJniObject can be implicitly +// constructed from the various subclasses - we can't provide an +// operator QJniObject() here as the class is not declared. +struct Object +{ + jobject _object; + constexpr operator jobject() const { return _object; } +}; + } // namespace QtJniTypes +#define Q_DECLARE_JNI_TYPE(Type, Signature) \ +namespace QtJniTypes { \ +struct Type : Object \ +{ \ + constexpr Type(jobject o) noexcept : Object{o} {} \ +}; \ +} \ +template<> \ +constexpr auto QtJniTypes::typeSignature() \ +{ \ + static_assert((Signature[0] == 'L' || Signature[0] == '[') \ + && Signature[sizeof(Signature) - 2] == ';', \ + "Type signature needs to start with 'L' or '['" \ + " and end with ';'"); \ + return QtJniTypes::String(Signature); \ +} \ + QT_END_NAMESPACE #endif diff --git a/src/corelib/platform/android/qandroidnativeinterface.cpp b/src/corelib/platform/android/qandroidnativeinterface.cpp index 09c24df410d..a93844139ba 100644 --- a/src/corelib/platform/android/qandroidnativeinterface.cpp +++ b/src/corelib/platform/android/qandroidnativeinterface.cpp @@ -38,14 +38,14 @@ static QBasicMutex g_pendingRunnablesMutex; QT_DEFINE_NATIVE_INTERFACE(QAndroidApplication); /*! - \fn jobject QNativeInterface::QAndroidApplication::context() + \fn jobject QNativeInterface::QAndroidApplication::context() Returns the Android context as a \c jobject. The context is an \c Activity if the main activity object is valid. Otherwise, the context is a \c Service. \since 6.2 */ -jobject QNativeInterface::QAndroidApplication::context() +QtJniTypes::Context QNativeInterface::QAndroidApplication::context() { return QtAndroidPrivate::context(); } diff --git a/src/plugins/platforms/android/androidjnimain.cpp b/src/plugins/platforms/android/androidjnimain.cpp index 0f417cdb238..3b5e6566304 100644 --- a/src/plugins/platforms/android/androidjnimain.cpp +++ b/src/plugins/platforms/android/androidjnimain.cpp @@ -43,9 +43,9 @@ static jmethodID m_loadClassMethodID = nullptr; static AAssetManager *m_assetManager = nullptr; static jobject m_assets = nullptr; static jobject m_resourcesObj = nullptr; -static jobject m_activityObject = nullptr; +static QtJniTypes::Activity m_activityObject = nullptr; static jmethodID m_createSurfaceMethodID = nullptr; -static jobject m_serviceObject = nullptr; +static QtJniTypes::Service m_serviceObject = nullptr; static jmethodID m_setSurfaceGeometryMethodID = nullptr; static jmethodID m_destroySurfaceMethodID = nullptr; @@ -159,12 +159,12 @@ namespace QtAndroid return m_applicationClass; } - jobject activity() + QtJniTypes::Activity activity() { return m_activityObject; } - jobject service() + QtJniTypes::Service service() { return m_serviceObject; } diff --git a/src/plugins/platforms/android/androidjnimain.h b/src/plugins/platforms/android/androidjnimain.h index 1425d2173cc..8d05e31f667 100644 --- a/src/plugins/platforms/android/androidjnimain.h +++ b/src/plugins/platforms/android/androidjnimain.h @@ -11,6 +11,7 @@ #include #include +#include QT_BEGIN_NAMESPACE @@ -49,8 +50,8 @@ namespace QtAndroid jobject assets(); AAssetManager *assetManager(); jclass applicationClass(); - jobject activity(); - jobject service(); + QtJniTypes::Activity activity(); + QtJniTypes::Service service(); // Keep synchronized with flags in ActivityDelegate.java enum SystemUiVisibility { diff --git a/tests/auto/corelib/kernel/qjnitypes/tst_qjnitypes.cpp b/tests/auto/corelib/kernel/qjnitypes/tst_qjnitypes.cpp index 49835a0e452..c4abfe399a4 100644 --- a/tests/auto/corelib/kernel/qjnitypes/tst_qjnitypes.cpp +++ b/tests/auto/corelib/kernel/qjnitypes/tst_qjnitypes.cpp @@ -40,6 +40,11 @@ static_assert(QtJniTypes::typeSignature() == "Lorg/qtproject/qt/a static_assert(QtJniTypes::typeSignature() != "Ljava/lang/Object;"); static_assert(!(QtJniTypes::typeSignature() == "X")); +Q_DECLARE_JNI_TYPE(JavaType, "Lorg/qtproject/qt/JavaType;"); +static_assert(QtJniTypes::typeSignature() == "Lorg/qtproject/qt/JavaType;"); +Q_DECLARE_JNI_TYPE(ArrayType, "[Lorg/qtproject/qt/ArrayType;") +static_assert(QtJniTypes::typeSignature() == "[Lorg/qtproject/qt/ArrayType;"); + static_assert(QtJniTypes::fieldSignature() == "I"); static_assert(QtJniTypes::fieldSignature() != "X"); static_assert(QtJniTypes::fieldSignature() != "Ljava/lang/Object;"); @@ -57,6 +62,8 @@ static_assert(QtJniTypes::methodSignature() == "(I)V"); static_assert(QtJniTypes::methodSignature() == "(ILjava/lang/String;)V"); static_assert(QtJniTypes::methodSignature() == "(ILjava/lang/Class;)J"); static_assert(QtJniTypes::methodSignature() == "(ILjava/lang/String;)Ljava/lang/Object;"); +static_assert(QtJniTypes::methodSignature() + == "(ILjava/lang/String;)Lorg/qtproject/qt/JavaType;"); static_assert(QtJniTypes::isPrimitiveType()); static_assert(QtJniTypes::isPrimitiveType()); @@ -66,6 +73,7 @@ static_assert(!QtJniTypes::isPrimitiveType()); static_assert(!QtJniTypes::isObjectType()); static_assert(!QtJniTypes::isObjectType()); static_assert(QtJniTypes::isObjectType()); +static_assert(QtJniTypes::isObjectType()); static_assert(QtJniTypes::isObjectType()); static_assert(QtJniTypes::String("ABCDE").startsWith("ABC")); @@ -86,6 +94,7 @@ static_assert(!QtJniTypes::String("ABCDE").endsWith('F')); void tst_QJniTypes::initTestCase() { + } QTEST_MAIN(tst_QJniTypes)