diff options
Diffstat (limited to 'src/corelib/kernel')
| -rw-r--r-- | src/corelib/kernel/qjniarray.h | 4 | ||||
| -rw-r--r-- | src/corelib/kernel/qjnienvironment.cpp | 8 | ||||
| -rw-r--r-- | src/corelib/kernel/qjnienvironment.h | 1 | ||||
| -rw-r--r-- | src/corelib/kernel/qjniobject.cpp | 135 | ||||
| -rw-r--r-- | src/corelib/kernel/qjniobject.h | 523 | ||||
| -rw-r--r-- | src/corelib/kernel/qmetacontainer.cpp | 8 | ||||
| -rw-r--r-- | src/corelib/kernel/qobject.cpp | 35 | ||||
| -rw-r--r-- | src/corelib/kernel/qpermissions.cpp | 6 |
8 files changed, 466 insertions, 254 deletions
diff --git a/src/corelib/kernel/qjniarray.h b/src/corelib/kernel/qjniarray.h index 13349688d20..97d0cd37682 100644 --- a/src/corelib/kernel/qjniarray.h +++ b/src/corelib/kernel/qjniarray.h @@ -872,7 +872,7 @@ auto QJniArrayBase::makeArray(List &&list, NewFn &&newArray, SetFn &&setRegion) const size_type length = size_type(std::size(list)); JNIEnv *env = QJniEnvironment::getJniEnv(); auto localArray = (env->*newArray)(length); - if (QJniEnvironment::checkAndClearExceptions(env)) { + if (env->ExceptionCheck()) { if (localArray) env->DeleteLocalRef(localArray); return QJniArray<ElementType>(); @@ -916,7 +916,7 @@ auto QJniArrayBase::makeObjectArray(List &&list) elementClass = env->GetObjectClass(*std::begin(list)); } auto localArray = env->NewObjectArray(length, elementClass, nullptr); - if (QJniEnvironment::checkAndClearExceptions(env)) { + if (env->ExceptionCheck()) { if (localArray) env->DeleteLocalRef(localArray); return ResultType(); diff --git a/src/corelib/kernel/qjnienvironment.cpp b/src/corelib/kernel/qjnienvironment.cpp index b4f8497ddda..1ee658fd18d 100644 --- a/src/corelib/kernel/qjnienvironment.cpp +++ b/src/corelib/kernel/qjnienvironment.cpp @@ -559,4 +559,12 @@ bool QJniEnvironment::checkAndClearExceptions(JNIEnv *env, QJniEnvironment::Outp return false; } +/*! + Returns the stack trace that resulted in \a exception being thrown. +*/ +QStringList QJniEnvironment::stackTrace(jthrowable exception) +{ + return exceptionMessage(getJniEnv(), exception).split(u'\n'); +} + QT_END_NAMESPACE diff --git a/src/corelib/kernel/qjnienvironment.h b/src/corelib/kernel/qjnienvironment.h index b473f75bed1..a5f3700b1f0 100644 --- a/src/corelib/kernel/qjnienvironment.h +++ b/src/corelib/kernel/qjnienvironment.h @@ -89,6 +89,7 @@ public: static bool checkAndClearExceptions(JNIEnv *env, OutputMode outputMode = OutputMode::Verbose); static JNIEnv *getJniEnv(); + static QStringList stackTrace(jthrowable exception); private: Q_DISABLE_COPY_MOVE(QJniEnvironment) diff --git a/src/corelib/kernel/qjniobject.cpp b/src/corelib/kernel/qjniobject.cpp index 8f3da9a8595..59117bd01d4 100644 --- a/src/corelib/kernel/qjniobject.cpp +++ b/src/corelib/kernel/qjniobject.cpp @@ -348,7 +348,10 @@ static inline QByteArray cacheKey(Args &&...args) return (QByteArrayView(":") + ... + QByteArrayView(args)); } -typedef QHash<QByteArray, jclass> JClassHash; +struct JClassHash : QHash<QByteArray, jclass> +{ + jmethodID loadClassMethod = 0; +}; Q_GLOBAL_STATIC(JClassHash, cachedClasses) Q_GLOBAL_STATIC(QReadWriteLock, cachedClassesLock) @@ -391,29 +394,22 @@ bool QJniObjectPrivate::isJString(JNIEnv *env) const */ static QJniObject getCleanJniObject(jobject object, JNIEnv *env) { - if (QJniEnvironment::checkAndClearExceptions(env) || !object) { - if (object) - env->DeleteLocalRef(object); + if (!object || env->ExceptionCheck()) return QJniObject(); - } - QJniObject res(object); - env->DeleteLocalRef(object); - return res; + return QJniObject::fromLocalRef(object); } -/*! - \internal - \a className must be slash-encoded -*/ -jclass QtAndroidPrivate::findClass(const char *className, JNIEnv *env) +namespace { + +jclass loadClassHelper(const QByteArray &className, JNIEnv *env) { Q_ASSERT(env); QByteArray classNameArray(className); #ifdef QT_DEBUG if (classNameArray.contains('.')) { qWarning("QtAndroidPrivate::findClass: className '%s' should use slash separators!", - className); + className.constData()); } #endif classNameArray.replace('.', '/'); @@ -442,21 +438,40 @@ jclass QtAndroidPrivate::findClass(const char *className, JNIEnv *env) if (!clazz) { // Wrong class loader, try our own - QJniObject classLoader(QtAndroidPrivate::classLoader()); - if (!classLoader.isValid()) + jobject classLoader = QtAndroidPrivate::classLoader(); + if (!classLoader) return nullptr; + if (!cachedClasses->loadClassMethod) { + jclass classLoaderClass = env->GetObjectClass(classLoader); + if (!classLoaderClass) + return nullptr; + cachedClasses->loadClassMethod = + env->GetMethodID(classLoaderClass, + "loadClass", "(Ljava/lang/String;)Ljava/lang/Class;"); + env->DeleteLocalRef(classLoaderClass); + if (!cachedClasses->loadClassMethod) { + qCritical("Couldn't find the 'loadClass' method in the Qt class loader"); + return nullptr; + } + } + // ClassLoader::loadClass on the other hand wants the binary name of the class, // e.g. dot-separated. In testing it works also with /, but better to stick to // the specification. const QString binaryClassName = QString::fromLatin1(className).replace(u'/', u'.'); - jstring classNameObject = env->NewString(reinterpret_cast<const jchar*>(binaryClassName.constData()), - binaryClassName.length()); - QJniObject classObject = classLoader.callMethod<jclass>("loadClass", classNameObject); + jstring classNameObject = env->NewString(binaryClassName.utf16(), binaryClassName.length()); + jobject classObject = env->CallObjectMethod(classLoader, + cachedClasses->loadClassMethod, + classNameObject); env->DeleteLocalRef(classNameObject); - if (!QJniEnvironment::checkAndClearExceptions(env) && classObject.isValid()) - clazz = static_cast<jclass>(env->NewGlobalRef(classObject.object())); + if (classObject && !env->ExceptionCheck()) { + clazz = static_cast<jclass>(env->NewGlobalRef(classObject)); + env->DeleteLocalRef(classObject); + } + // Clearing the exception is the caller's responsibility (see + // QtAndroidPrivate::findClass()) and QJniObject::loadClass{KeepExceptions} } if (clazz) @@ -465,11 +480,32 @@ jclass QtAndroidPrivate::findClass(const char *className, JNIEnv *env) return clazz; } +} // unnamed namespace + +/*! + \internal + \a className must be slash-encoded +*/ +jclass QtAndroidPrivate::findClass(const char *className, JNIEnv *env) +{ + jclass clazz = loadClassHelper(className, env); + if (!clazz) + QJniEnvironment::checkAndClearExceptions(env); + return clazz; +} + jclass QJniObject::loadClass(const QByteArray &className, JNIEnv *env) { return QtAndroidPrivate::findClass(className, env); } +jclass QJniObject::loadClassKeepExceptions(const QByteArray &className, JNIEnv *env) +{ + return loadClassHelper(className, env); +} + + + typedef QHash<QByteArray, jmethodID> JMethodIDHash; Q_GLOBAL_STATIC(JMethodIDHash, cachedMethodID) Q_GLOBAL_STATIC(QReadWriteLock, cachedMethodIDLock) @@ -483,9 +519,6 @@ jmethodID QJniObject::getMethodID(JNIEnv *env, jmethodID id = isStatic ? env->GetStaticMethodID(clazz, name, signature) : env->GetMethodID(clazz, name, signature); - if (QJniEnvironment::checkAndClearExceptions(env)) - return nullptr; - return id; } @@ -525,7 +558,8 @@ jmethodID QJniObject::getCachedMethodID(JNIEnv *env, jmethodID id = getMethodID(env, clazz, name, signature, isStatic); - cachedMethodID->insert(key, id); + if (id) + cachedMethodID->insert(key, id); return id; } } @@ -549,9 +583,6 @@ jfieldID QJniObject::getFieldID(JNIEnv *env, jfieldID id = isStatic ? env->GetStaticFieldID(clazz, name, signature) : env->GetFieldID(clazz, name, signature); - if (QJniEnvironment::checkAndClearExceptions(env)) - return nullptr; - return id; } @@ -583,7 +614,8 @@ jfieldID QJniObject::getCachedFieldID(JNIEnv *env, jfieldID id = getFieldID(env, clazz, name, signature, isStatic); - cachedFieldID->insert(key, id); + if (id) + cachedFieldID->insert(key, id); return id; } } @@ -1008,15 +1040,15 @@ QJniObject QJniObject::callObjectMethod(const char *methodName, const char *sign { JNIEnv *env = jniEnv(); jmethodID id = getCachedMethodID(env, methodName, signature); - if (id) { - va_list args; - va_start(args, signature); - QJniObject res = getCleanJniObject(env->CallObjectMethodV(d->m_jobject, id, args), env); - va_end(args); - return res; - } + va_list args; + va_start(args, signature); + // can't go back from variadic arguments to variadic templates + jobject object = id ? jniEnv()->CallObjectMethodV(javaObject(), id, args) : nullptr; + QJniObject res = getCleanJniObject(object, env); + va_end(args); - return QJniObject(); + QJniEnvironment::checkAndClearExceptions(env); + return res; } /*! @@ -1150,7 +1182,7 @@ QJniObject QJniObject::callStaticObjectMethod(jclass clazz, jmethodID methodId, */ /*! - \fn template <typename T> void QJniObject::setStaticField(const char *className, const char *fieldName, const char *signature, T value); + \fn template <typename Ret, typename Type> auto QJniObject::setStaticField(const char *className, const char *fieldName, const char *signature, Type value); Sets the static field \a fieldName on the class \a className to \a value using the setter with \a signature. @@ -1158,7 +1190,7 @@ QJniObject QJniObject::callStaticObjectMethod(jclass clazz, jmethodID methodId, */ /*! - \fn template <typename T> void QJniObject::setStaticField(jclass clazz, const char *fieldName, const char *signature, T value); + \fn template <typename Ret, typename Type> auto QJniObject::setStaticField(jclass clazz, const char *fieldName, const char *signature, Type value); Sets the static field \a fieldName on the class \a clazz to \a value using the setter with \a signature. @@ -1196,19 +1228,19 @@ QJniObject QJniObject::callStaticObjectMethod(jclass clazz, jmethodID methodId, */ /*! - \fn template <typename T> void QJniObject::setStaticField(const char *className, const char *fieldName, T value) + \fn template <typename Ret, typename Type> auto QJniObject::setStaticField(const char *className, const char *fieldName, Type value) Sets the static field \a fieldName of the class \a className to \a value. */ /*! - \fn template <typename T> void QJniObject::setStaticField(jclass clazz, const char *fieldName, T value) + \fn template <typename Ret, typename Type> auto QJniObject::setStaticField(jclass clazz, const char *fieldName, Type value) Sets the static field \a fieldName of the class \a clazz to \a value. */ /*! - \fn template <typename Klass, typename T> auto QJniObject::setStaticField(const char *fieldName, T value) + \fn template <typename Klass, typename Ret, typename Type> auto QJniObject::setStaticField(const char *fieldName, Type value) Sets the static field \a fieldName of the class \c Klass to \a value. @@ -1263,11 +1295,16 @@ QJniObject QJniObject::getStaticObjectField(jclass clazz, const char *fieldName, { JNIEnv *env = QJniEnvironment::getJniEnv(); jfieldID id = getFieldID(env, clazz, fieldName, signature, true); - return getCleanJniObject(env->GetStaticObjectField(clazz, id), env); + + const auto clearExceptions = qScopeGuard([env]{ + QJniEnvironment::checkAndClearExceptions(env); + }); + + return getCleanJniObject(getStaticObjectFieldImpl(env, clazz, id), env); } /*! - \fn template <typename T> void QJniObject::setField(const char *fieldName, const char *signature, T value) + \fn template <typename Ret, typename Type> void QJniObject::setField(const char *fieldName, const char *signature, Type value) Sets the value of \a fieldName with \a signature to \a value. @@ -1304,14 +1341,16 @@ QJniObject QJniObject::getObjectField(const char *fieldName, const char *signatu { JNIEnv *env = jniEnv(); jfieldID id = getCachedFieldID(env, fieldName, signature); - if (!id) - return QJniObject(); - return getCleanJniObject(env->GetObjectField(d->m_jobject, id), env); + const auto clearExceptions = qScopeGuard([env]{ + QJniEnvironment::checkAndClearExceptions(env); + }); + + return getCleanJniObject(getObjectFieldImpl(env, id), env); } /*! - \fn template <typename T> void QJniObject::setField(const char *fieldName, T value) + \fn template <typename Ret, typename Type> void QJniObject::setField(const char *fieldName, Type value) Sets the value of \a fieldName to \a value. diff --git a/src/corelib/kernel/qjniobject.h b/src/corelib/kernel/qjniobject.h index 236a49544be..06dfc328b4b 100644 --- a/src/corelib/kernel/qjniobject.h +++ b/src/corelib/kernel/qjniobject.h @@ -27,10 +27,29 @@ struct StoresGlobalRefTest<T, std::void_t<decltype(std::declval<T>().object())>> : std::is_same<decltype(std::declval<T>().object()), jobject> {}; -template <typename ...Args> +// detect if a type is std::expected-like +template <typename R, typename = void> +struct CallerHandlesException : std::false_type { + using value_type = R; +}; +template <typename R> +struct CallerHandlesException<R, std::void_t<typename R::unexpected_type, + typename R::value_type, + typename R::error_type>> : std::true_type +{ + using value_type = typename R::value_type; +}; + +template <typename ReturnType> +static constexpr bool callerHandlesException = CallerHandlesException<ReturnType>::value; + +template <typename Ret, typename ...Args> struct LocalFrame { + using ReturnType = Ret; + mutable JNIEnv *env; bool hasFrame = false; + explicit LocalFrame(JNIEnv *env = nullptr) noexcept : env(env) { @@ -52,9 +71,12 @@ struct LocalFrame { env = QJniEnvironment::getJniEnv(); return env; } - bool checkAndClearExceptions() + bool checkAndClearExceptions() const { - return env ? QJniEnvironment::checkAndClearExceptions(env) : false; + if constexpr (callerHandlesException<ReturnType>) + return false; + else + return QJniEnvironment::checkAndClearExceptions(jniEnv()); } template <typename T> auto convertToJni(T &&value) @@ -79,13 +101,46 @@ struct LocalFrame { using Type = q20::remove_cvref_t<T>; return QtJniTypes::Traits<Type>::convertFromJni(std::move(object)); } + + template <typename T> + auto convertFromJni(jobject object); + + auto makeResult() + { + if constexpr (callerHandlesException<ReturnType>) { + JNIEnv *env = jniEnv(); + if (env->ExceptionCheck()) { + jthrowable exception = env->ExceptionOccurred(); + env->ExceptionClear(); + return ReturnType(typename ReturnType::unexpected_type(exception)); + } + return ReturnType(); + } else { + checkAndClearExceptions(); + } + } + + template <typename Value> + auto makeResult(Value &&value) + { + if constexpr (callerHandlesException<ReturnType>) { + auto maybeValue = makeResult(); + if (maybeValue) + return ReturnType(std::forward<Value>(value)); + return std::move(maybeValue); + } else { + checkAndClearExceptions(); + return std::forward<Value>(value); + } + } }; } } class Q_CORE_EXPORT QJniObject { - template <typename ...Args> using LocalFrame = QtJniTypes::Detail::LocalFrame<Args...>; + template <typename Ret, typename ...Args> using LocalFrame + = QtJniTypes::Detail::LocalFrame<Ret, Args...>; public: QJniObject(); @@ -97,12 +152,12 @@ public: #endif > explicit QJniObject(const char *className, Args &&...args) - : QJniObject(LocalFrame<Args...>{}, className, std::forward<Args>(args)...) + : QJniObject(LocalFrame<QJniObject, Args...>{}, className, std::forward<Args>(args)...) { } private: template<typename ...Args> - explicit QJniObject(LocalFrame<Args...> localFrame, const char *className, Args &&...args) + explicit QJniObject(LocalFrame<QJniObject, Args...> localFrame, const char *className, Args &&...args) : QJniObject(className, QtJniTypes::constructorSignature<Args...>().data(), localFrame.convertToJni(std::forward<Args>(args))...) { @@ -130,13 +185,23 @@ public: void swap(QJniObject &other) noexcept { d.swap(other.d); } - template<typename Class, typename ...Args> - static inline QJniObject construct(Args &&...args) + template<typename Class, typename ...Args +#ifndef Q_QDOC + , QtJniTypes::IfValidSignatureTypes<Class, Args...> = true +#endif + > + static inline auto construct(Args &&...args) { - LocalFrame<Args...> frame; - return QJniObject(QtJniTypes::Traits<Class>::className().data(), - QtJniTypes::constructorSignature<Args...>().data(), - frame.convertToJni(std::forward<Args>(args))...); + LocalFrame<Class, Args...> frame; + jclass clazz = QJniObject::loadClassKeepExceptions(QtJniTypes::Traits<Class>::className().data(), + frame.jniEnv()); + auto res = clazz + ? QJniObject(clazz, QtJniTypes::constructorSignature<Args...>().data(), + frame.convertToJni(std::forward<Args>(args))...) + : QtJniTypes::Detail::callerHandlesException<Class> + ? QJniObject(Qt::Initialization::Uninitialized) + : QJniObject(); + return frame.makeResult(std::move(res)); } jobject object() const; @@ -149,47 +214,49 @@ public: jclass objectClass() const; QByteArray className() const; - template <typename Ret = void, typename ...Args + template <typename ReturnType = void, typename ...Args #ifndef Q_QDOC - , QtJniTypes::IfValidFieldType<Ret> = true + , QtJniTypes::IfValidFieldType<ReturnType> = true #endif > auto callMethod(const char *methodName, const char *signature, Args &&...args) const { - LocalFrame<Args...> frame(jniEnv()); + using Ret = typename QtJniTypes::Detail::CallerHandlesException<ReturnType>::value_type; + LocalFrame<ReturnType, Args...> frame(jniEnv()); + jmethodID id = getCachedMethodID(frame.jniEnv(), methodName, signature); + if constexpr (QtJniTypes::isObjectType<Ret>()) { - return frame.template convertFromJni<Ret>(callObjectMethod(methodName, signature, - frame.convertToJni(std::forward<Args>(args))...)); + return frame.makeResult(frame.template convertFromJni<Ret>(callObjectMethodImpl( + id, frame.convertToJni(std::forward<Args>(args))...)) + ); } else { - jmethodID id = getCachedMethodID(frame.jniEnv(), methodName, signature); if (id) { if constexpr (std::is_same_v<Ret, void>) { callVoidMethodV(frame.jniEnv(), id, frame.convertToJni(std::forward<Args>(args))...); - frame.checkAndClearExceptions(); } else { Ret res{}; callMethodForType<Ret>(frame.jniEnv(), res, object(), id, frame.convertToJni(std::forward<Args>(args))...); - if (frame.checkAndClearExceptions()) - res = {}; - return res; + return frame.makeResult(res); } } if constexpr (!std::is_same_v<Ret, void>) - return Ret{}; + return frame.makeResult(Ret{}); + else + return frame.makeResult(); } } - template <typename Ret = void, typename ...Args + template <typename ReturnType = void, typename ...Args #ifndef Q_QDOC - , QtJniTypes::IfValidSignatureTypes<Ret, Args...> = true + , QtJniTypes::IfValidSignatureTypes<ReturnType, Args...> = true #endif > auto callMethod(const char *methodName, Args &&...args) const { - constexpr auto signature = QtJniTypes::methodSignature<Ret, Args...>(); - return callMethod<Ret>(methodName, signature.data(), std::forward<Args>(args)...); + constexpr auto signature = QtJniTypes::methodSignature<ReturnType, Args...>(); + return callMethod<ReturnType>(methodName, signature.data(), std::forward<Args>(args)...); } template <typename Ret, typename ...Args @@ -201,9 +268,11 @@ public: { QtJniTypes::assertObjectType<Ret>(); constexpr auto signature = QtJniTypes::methodSignature<Ret, Args...>(); - LocalFrame<Args...> frame(jniEnv()); - return frame.template convertFromJni<Ret>(callObjectMethod(methodName, signature, + LocalFrame<Ret, Args...> frame(jniEnv()); + auto object = frame.template convertFromJni<Ret>(callObjectMethod(methodName, signature, frame.convertToJni(std::forward<Args>(args))...)); + frame.checkAndClearExceptions(); + return object; } QJniObject callObjectMethod(const char *methodName, const char *signature, ...) const; @@ -211,90 +280,93 @@ public: template <typename Ret = void, typename ...Args> static auto callStaticMethod(const char *className, const char *methodName, const char *signature, Args &&...args) { - JNIEnv *env = QJniEnvironment::getJniEnv(); - jclass clazz = QJniObject::loadClass(className, env); + LocalFrame<Ret, Args...> frame; + jclass clazz = QJniObject::loadClass(className, frame.jniEnv()); return callStaticMethod<Ret>(clazz, methodName, signature, std::forward<Args>(args)...); } template <typename Ret = void, typename ...Args> static auto callStaticMethod(jclass clazz, const char *methodName, const char *signature, Args &&...args) { - JNIEnv *env = QJniEnvironment::getJniEnv(); - jmethodID id = clazz ? getMethodID(env, clazz, methodName, signature, true) + LocalFrame<Ret, Args...> frame; + jmethodID id = clazz ? getMethodID(frame.jniEnv(), clazz, methodName, signature, true) : 0; return callStaticMethod<Ret>(clazz, id, std::forward<Args>(args)...); } - template <typename Ret = void, typename ...Args + template <typename ReturnType = void, typename ...Args #ifndef Q_QDOC - , QtJniTypes::IfValidFieldType<Ret> = true + , QtJniTypes::IfValidFieldType<ReturnType> = true #endif > static auto callStaticMethod(jclass clazz, jmethodID methodId, Args &&...args) { - LocalFrame<Args...> frame; + using Ret = typename QtJniTypes::Detail::CallerHandlesException<ReturnType>::value_type; + LocalFrame<ReturnType, Args...> frame; if constexpr (QtJniTypes::isObjectType<Ret>()) { - return frame.template convertFromJni<Ret>(callStaticObjectMethod(clazz, methodId, - frame.convertToJni(std::forward<Args>(args))...)); + return frame.makeResult(frame.template convertFromJni<Ret>(callStaticObjectMethod( + clazz, methodId, + frame.convertToJni(std::forward<Args>(args))...)) + ); } else { if (clazz && methodId) { if constexpr (std::is_same_v<Ret, void>) { callStaticMethodForVoid(frame.jniEnv(), clazz, methodId, frame.convertToJni(std::forward<Args>(args))...); - frame.checkAndClearExceptions(); } else { Ret res{}; callStaticMethodForType<Ret>(frame.jniEnv(), res, clazz, methodId, frame.convertToJni(std::forward<Args>(args))...); - if (frame.checkAndClearExceptions()) - res = {}; - return res; + return frame.makeResult(res); } } if constexpr (!std::is_same_v<Ret, void>) - return Ret{}; + return frame.makeResult(Ret{}); + else + return frame.makeResult(); } } - template <typename Ret = void, typename ...Args + template <typename ReturnType = void, typename ...Args #ifndef Q_QDOC - , QtJniTypes::IfValidSignatureTypes<Ret, Args...> = true + , QtJniTypes::IfValidSignatureTypes<ReturnType, Args...> = true #endif > static auto callStaticMethod(const char *className, const char *methodName, Args &&...args) { - JNIEnv *env = QJniEnvironment::getJniEnv(); - jclass clazz = QJniObject::loadClass(className, env); - const jmethodID id = clazz ? getMethodID(env, clazz, methodName, + using Ret = typename QtJniTypes::Detail::CallerHandlesException<ReturnType>::value_type; + LocalFrame<Ret, Args...> frame; + jclass clazz = QJniObject::loadClass(className, frame.jniEnv()); + const jmethodID id = clazz ? getMethodID(frame.jniEnv(), clazz, methodName, QtJniTypes::methodSignature<Ret, Args...>().data(), true) : 0; - return callStaticMethod<Ret>(clazz, id, std::forward<Args>(args)...); + return callStaticMethod<ReturnType>(clazz, id, std::forward<Args>(args)...); } - template <typename Ret = void, typename ...Args + template <typename ReturnType = void, typename ...Args #ifndef Q_QDOC - , QtJniTypes::IfValidSignatureTypes<Ret, Args...> = true + , QtJniTypes::IfValidSignatureTypes<ReturnType, Args...> = true #endif > static auto callStaticMethod(jclass clazz, const char *methodName, Args &&...args) { - constexpr auto signature = QtJniTypes::methodSignature<Ret, Args...>(); - return callStaticMethod<Ret>(clazz, methodName, signature.data(), std::forward<Args>(args)...); + constexpr auto signature = QtJniTypes::methodSignature<ReturnType, Args...>(); + return callStaticMethod<ReturnType>(clazz, methodName, signature.data(), std::forward<Args>(args)...); } - template <typename Klass, typename Ret = void, typename ...Args + template <typename Klass, typename ReturnType = void, typename ...Args #ifndef Q_QDOC - , QtJniTypes::IfValidSignatureTypes<Ret, Args...> = true + , QtJniTypes::IfValidSignatureTypes<ReturnType, Args...> = true #endif > static auto callStaticMethod(const char *methodName, Args &&...args) { - JNIEnv *env = QJniEnvironment::getJniEnv(); + LocalFrame<ReturnType, Args...> frame; const jclass clazz = QJniObject::loadClass(QtJniTypes::Traits<Klass>::className().data(), - env); - const jmethodID id = clazz ? getMethodID(env, clazz, methodName, - QtJniTypes::methodSignature<Ret, Args...>().data(), true) + frame.jniEnv()); + const jmethodID id = clazz ? getMethodID(frame.jniEnv(), clazz, methodName, + QtJniTypes::methodSignature<ReturnType, Args...>().data(), true) : 0; - return callStaticMethod<Ret>(clazz, id, std::forward<Args>(args)...); + return callStaticMethod<ReturnType>(clazz, id, std::forward<Args>(args)...); } static QJniObject callStaticObjectMethod(const char *className, const char *methodName, @@ -315,7 +387,7 @@ public: { QtJniTypes::assertObjectType<Ret>(); constexpr auto signature = QtJniTypes::methodSignature<Ret, Args...>(); - LocalFrame<Args...> frame; + LocalFrame<QJniObject, Args...> frame; return frame.template convertFromJni<Ret>(callStaticObjectMethod(className, methodName, signature.data(), frame.convertToJni(std::forward<Args>(args))...)); } @@ -329,98 +401,95 @@ public: { QtJniTypes::assertObjectType<Ret>(); constexpr auto signature = QtJniTypes::methodSignature<Ret, Args...>(); - LocalFrame<Args...> frame; + LocalFrame<QJniObject, Args...> frame; return frame.template convertFromJni<Ret>(callStaticObjectMethod(clazz, methodName, signature.data(), - frame.convertToJni(std::forward<Args>(args))...)); + frame.convertToJni(std::forward<Args>(args))...)); } - template <typename T + template <typename Type #ifndef Q_QDOC - , QtJniTypes::IfValidFieldType<T> = true + , QtJniTypes::IfValidFieldType<Type> = true #endif > auto getField(const char *fieldName) const { - LocalFrame<T> frame(jniEnv()); + using T = typename QtJniTypes::Detail::CallerHandlesException<Type>::value_type; + LocalFrame<Type, T> frame(jniEnv()); + constexpr auto signature = QtJniTypes::fieldSignature<T>(); + jfieldID id = getCachedFieldID(frame.jniEnv(), fieldName, signature); + if constexpr (QtJniTypes::isObjectType<T>()) { - return frame.template convertFromJni<T>(getObjectField<T>(fieldName)); + return frame.makeResult(frame.template convertFromJni<T>(getObjectFieldImpl( + frame.jniEnv(), id)) + ); } else { T res{}; - constexpr auto signature = QtJniTypes::fieldSignature<T>(); - jfieldID id = getCachedFieldID(frame.jniEnv(), fieldName, signature); - if (id) { + if (id) getFieldForType<T>(frame.jniEnv(), res, object(), id); - if (frame.checkAndClearExceptions()) - res = {}; - } - return res; + return frame.makeResult(res); } } - template <typename T + template <typename Klass, typename T #ifndef Q_QDOC , QtJniTypes::IfValidFieldType<T> = true #endif > - static auto getStaticField(const char *className, const char *fieldName) + static auto getStaticField(const char *fieldName) { - LocalFrame<T> frame; - if constexpr (QtJniTypes::isObjectType<T>()) { - return frame.template convertFromJni<T>(getStaticObjectField<T>(className, fieldName)); - } else { - jclass clazz = QJniObject::loadClass(className, frame.jniEnv()); - if (!clazz) - return T{}; - return getStaticField<T>(clazz, fieldName); - } + return getStaticField<T>(QtJniTypes::Traits<Klass>::className(), fieldName); } template <typename T #ifndef Q_QDOC - , QtJniTypes::IfValidFieldType<T> = true + , std::enable_if_t<QtJniTypes::isObjectType<T>(), bool> = true #endif > - static auto getStaticField(jclass clazz, const char *fieldName) + QJniObject getObjectField(const char *fieldName) const { - LocalFrame<T> frame; - if constexpr (QtJniTypes::isObjectType<T>()) { - return frame.template convertFromJni<T>(getStaticObjectField<T>(clazz, fieldName)); - } else { - T res{}; - constexpr auto signature = QtJniTypes::fieldSignature<T>(); - jfieldID id = getFieldID(frame.jniEnv(), clazz, fieldName, signature, true); - if (id) { - getStaticFieldForType<T>(frame.jniEnv(), res, clazz, id); - if (frame.checkAndClearExceptions()) - res = {}; - } - return res; - } + constexpr auto signature = QtJniTypes::fieldSignature<T>(); + return getObjectField(fieldName, signature); } - template <typename Klass, typename T + QJniObject getObjectField(const char *fieldName, const char *signature) const; + + template <typename Type #ifndef Q_QDOC - , QtJniTypes::IfValidFieldType<T> = true + , QtJniTypes::IfValidFieldType<Type> = true #endif > - static auto getStaticField(const char *fieldName) + static auto getStaticField(const char *className, const char *fieldName) { - return getStaticField<T>(QtJniTypes::Traits<Klass>::className(), fieldName); + using T = typename QtJniTypes::Detail::CallerHandlesException<Type>::value_type; + LocalFrame<Type, T> frame; + jclass clazz = QJniObject::loadClass(className, frame.jniEnv()); + return getStaticField<Type>(clazz, fieldName); } - template <typename T + template <typename Type #ifndef Q_QDOC - , std::enable_if_t<QtJniTypes::isObjectType<T>(), bool> = true + , QtJniTypes::IfValidFieldType<Type> = true #endif > - QJniObject getObjectField(const char *fieldName) const + static auto getStaticField(jclass clazz, const char *fieldName) { + using T = typename QtJniTypes::Detail::CallerHandlesException<Type>::value_type; + LocalFrame<Type, T> frame; constexpr auto signature = QtJniTypes::fieldSignature<T>(); - return getObjectField(fieldName, signature); + jfieldID id = clazz ? getFieldID(frame.jniEnv(), clazz, fieldName, signature, true) + : nullptr; + if constexpr (QtJniTypes::isObjectType<T>()) { + return frame.makeResult(frame.template convertFromJni<T>(getStaticObjectFieldImpl( + frame.jniEnv(), clazz, id)) + ); + } else { + T res{}; + if (id) + getStaticFieldForType<T>(frame.jniEnv(), res, clazz, id); + return frame.makeResult(res); + } } - QJniObject getObjectField(const char *fieldName, const char *signature) const; - template <typename T #ifndef Q_QDOC , std::enable_if_t<QtJniTypes::isObjectType<T>(), bool> = true @@ -450,114 +519,122 @@ public: static QJniObject getStaticObjectField(jclass clazz, const char *fieldName, const char *signature); - template <typename T + template <typename Ret = void, typename Type #ifndef Q_QDOC - , QtJniTypes::IfValidFieldType<T> = true + , QtJniTypes::IfValidFieldType<Type> = true #endif > - void setField(const char *fieldName, T value) + auto setField(const char *fieldName, Type value) { + // handle old code explicitly specifying a non-return type for Ret + using T = std::conditional_t<!std::is_void_v<Ret> && !QtJniTypes::Detail::callerHandlesException<Ret>, + Ret, Type>; + LocalFrame<Ret, T> frame(jniEnv()); constexpr auto signature = QtJniTypes::fieldSignature<T>(); jfieldID id = getCachedFieldID(jniEnv(), fieldName, signature); - if (id) { + if (id) setFieldForType<T>(jniEnv(), object(), id, value); - QJniEnvironment::checkAndClearExceptions(jniEnv()); - } + return frame.makeResult(); } - template <typename T + template <typename Ret = void, typename Type #ifndef Q_QDOC - , QtJniTypes::IfValidFieldType<T> = true + , QtJniTypes::IfValidFieldType<Type> = true #endif > - void setField(const char *fieldName, const char *signature, T value) - { - jfieldID id = getCachedFieldID(jniEnv(), fieldName, signature); - if (id) { + auto setField(const char *fieldName, const char *signature, Type value) + { + // handle old code explicitly specifying a non-return type for Ret + using T = std::conditional_t<!std::is_void_v<Ret> && !QtJniTypes::Detail::callerHandlesException<Ret>, + Ret, Type>; + LocalFrame<Ret, T> frame(jniEnv()); + jfieldID id = getCachedFieldID(frame.jniEnv(), fieldName, signature); + if (id) setFieldForType<T>(jniEnv(), object(), id, value); - QJniEnvironment::checkAndClearExceptions(jniEnv()); - } + return frame.makeResult(); } - template <typename T + template <typename Ret = void, typename Type #ifndef Q_QDOC - , QtJniTypes::IfValidFieldType<T> = true + , QtJniTypes::IfValidFieldType<Type> = true #endif > - static void setStaticField(const char *className, const char *fieldName, T value) - { - LocalFrame<T> frame; - jclass clazz = QJniObject::loadClass(className, frame.jniEnv()); - if (!clazz) - return; - - constexpr auto signature = QtJniTypes::fieldSignature<T>(); - jfieldID id = getCachedFieldID(frame.jniEnv(), clazz, className, fieldName, - signature, true); - if (!id) - return; - - setStaticFieldForType<T>(frame.jniEnv(), clazz, id, value); - frame.checkAndClearExceptions(); + static auto setStaticField(const char *className, const char *fieldName, Type value) + { + // handle old code explicitly specifying a non-return type for Ret + using T = std::conditional_t<!std::is_void_v<Ret> && !QtJniTypes::Detail::callerHandlesException<Ret>, + Ret, Type>; + LocalFrame<Ret, T> frame; + if (jclass clazz = QJniObject::loadClass(className, frame.jniEnv())) { + constexpr auto signature = QtJniTypes::fieldSignature<q20::remove_cvref_t<T>>(); + jfieldID id = getCachedFieldID(frame.jniEnv(), clazz, className, fieldName, + signature, true); + if (id) + setStaticFieldForType<T>(frame.jniEnv(), clazz, id, value); + } + return frame.makeResult(); } - template <typename T + template <typename Ret = void, typename Type #ifndef Q_QDOC - , QtJniTypes::IfValidFieldType<T> = true + , QtJniTypes::IfValidFieldType<Type> = true #endif > - static void setStaticField(const char *className, const char *fieldName, - const char *signature, T value) - { - JNIEnv *env = QJniEnvironment::getJniEnv(); - jclass clazz = QJniObject::loadClass(className, env); - - if (!clazz) - return; - - jfieldID id = getCachedFieldID(env, clazz, className, fieldName, - signature, true); - if (id) { - setStaticFieldForType<T>(env, clazz, id, value); - QJniEnvironment::checkAndClearExceptions(env); + static auto setStaticField(const char *className, const char *fieldName, + const char *signature, Type value) + { + // handle old code explicitly specifying a non-return type for Ret + using T = std::conditional_t<!std::is_void_v<Ret> && !QtJniTypes::Detail::callerHandlesException<Ret>, + Ret, Type>; + LocalFrame<Ret, T> frame; + if (jclass clazz = QJniObject::loadClass(className, frame.jniEnv())) { + jfieldID id = getCachedFieldID(frame.jniEnv(), clazz, className, fieldName, + signature, true); + if (id) + setStaticFieldForType<T>(frame.jniEnv(), clazz, id, value); } + return frame.makeResult(); } - template <typename T + template <typename Ret = void, typename Type #ifndef Q_QDOC - , QtJniTypes::IfValidFieldType<T> = true + , QtJniTypes::IfValidFieldType<Type> = true #endif > - static void setStaticField(jclass clazz, const char *fieldName, - const char *signature, T value) + static auto setStaticField(jclass clazz, const char *fieldName, + const char *signature, Type value) { - JNIEnv *env = QJniEnvironment::getJniEnv(); - jfieldID id = getFieldID(env, clazz, fieldName, signature, true); + // handle old code explicitly specifying a non-return type for Ret + using T = std::conditional_t<!std::is_void_v<Ret> && !QtJniTypes::Detail::callerHandlesException<Ret>, + Ret, Type>; + LocalFrame<Ret, T> frame; + jfieldID id = getFieldID(frame.jniEnv(), clazz, fieldName, signature, true); - if (id) { - setStaticFieldForType<T>(env, clazz, id, value); - QJniEnvironment::checkAndClearExceptions(env); - } + if (id) + setStaticFieldForType<T>(frame.jniEnv(), clazz, id, value); + return frame.makeResult(); } - template <typename T + template <typename Ret = void, typename Type #ifndef Q_QDOC - , QtJniTypes::IfValidFieldType<T> = true + , QtJniTypes::IfValidFieldType<Type> = true #endif > - static void setStaticField(jclass clazz, const char *fieldName, T value) + static auto setStaticField(jclass clazz, const char *fieldName, Type value) { - setStaticField(clazz, fieldName, QtJniTypes::fieldSignature<T>(), value); + return setStaticField<Ret, Type>(clazz, fieldName, + QtJniTypes::fieldSignature<q20::remove_cvref_t<Type>>(), + value); } - template <typename Klass, typename T + template <typename Klass, typename Ret = void, typename Type #ifndef Q_QDOC - , QtJniTypes::IfValidFieldType<T> = true + , QtJniTypes::IfValidFieldType<Type> = true #endif > - static void setStaticField(const char *fieldName, T value) + static auto setStaticField(const char *fieldName, Type value) { - setStaticField(QtJniTypes::Traits<Klass>::className(), fieldName, value); + return setStaticField<Ret, Type>(QtJniTypes::Traits<Klass>::className(), fieldName, value); } static QJniObject fromString(const QString &string); @@ -583,6 +660,7 @@ protected: private: static jclass loadClass(const QByteArray &className, JNIEnv *env); + static jclass loadClassKeepExceptions(const QByteArray &className, JNIEnv *env); #if QT_CORE_REMOVED_SINCE(6, 7) // these need to stay in the ABI as they were used in inline methods before 6.7 @@ -620,12 +698,25 @@ private: template<typename T> static constexpr void callMethodForType(JNIEnv *env, T &res, jobject obj, jmethodID id, ...) { + if (!id) + return; + va_list args = {}; va_start(args, id); QtJniTypes::Caller<T>::callMethodForType(env, res, obj, id, args); va_end(args); } + jobject callObjectMethodImpl(jmethodID method, ...) const + { + va_list args; + va_start(args, method); + jobject res = method ? jniEnv()->CallObjectMethodV(javaObject(), method, args) + : nullptr; + va_end(args); + return res; + } + template<typename T> static constexpr void callStaticMethodForType(JNIEnv *env, T &res, jclass clazz, jmethodID id, ...) @@ -652,6 +743,9 @@ private: template<typename T> static constexpr void getFieldForType(JNIEnv *env, T &res, jobject obj, jfieldID id) { + if (!id) + return; + QtJniTypes::Caller<T>::getFieldForType(env, res, obj, id); } @@ -661,22 +755,42 @@ private: QtJniTypes::Caller<T>::getStaticFieldForType(env, res, clazz, id); } - template<typename T> - static constexpr void setFieldForType(JNIEnv *env, jobject obj, jfieldID id, T value) + template<typename Type> + static constexpr void setFieldForType(JNIEnv *env, jobject obj, jfieldID id, Type value) { + if (!id) + return; + + using T = q20::remove_cvref_t<Type>; if constexpr (QtJniTypes::isObjectType<T>()) { - LocalFrame<T> frame(env); + LocalFrame<T, T> frame(env); env->SetObjectField(obj, id, static_cast<jobject>(frame.convertToJni(value))); } else { - QtJniTypes::Caller<T>::setFieldForType(env, obj, id, value); + using ValueType = typename QtJniTypes::Detail::CallerHandlesException<T>::value_type; + QtJniTypes::Caller<ValueType>::setFieldForType(env, obj, id, value); } } - template<typename T> - static constexpr void setStaticFieldForType(JNIEnv *env, jclass clazz, jfieldID id, T value) + jobject getObjectFieldImpl(JNIEnv *env, jfieldID field) const + { + return field ? env->GetObjectField(javaObject(), field) : nullptr; + } + + static jobject getStaticObjectFieldImpl(JNIEnv *env, jclass clazz, jfieldID field) + { + return clazz && field ? env->GetStaticObjectField(clazz, field) + : nullptr; + } + + template<typename Type> + static constexpr void setStaticFieldForType(JNIEnv *env, jclass clazz, jfieldID id, Type value) { + if (!clazz || !id) + return; + + using T = q20::remove_cvref_t<Type>; if constexpr (QtJniTypes::isObjectType<T>()) { - LocalFrame<T> frame(env); + LocalFrame<T, T> frame(env); env->SetStaticObjectField(clazz, id, static_cast<jobject>(frame.convertToJni(value))); } else { QtJniTypes::Caller<T>::setStaticFieldForType(env, clazz, id, value); @@ -797,14 +911,14 @@ public: { return QJniObject::getStaticField<Class, T>(field); } - template <typename T + template <typename Ret = void, typename T #ifndef Q_QDOC , QtJniTypes::IfValidFieldType<T> = true #endif > - static void setStaticField(const char *field, T &&value) + static auto setStaticField(const char *field, T &&value) { - QJniObject::setStaticField<Class, T>(field, std::forward<T>(value)); + return QJniObject::setStaticField<Class, Ret, T>(field, std::forward<T>(value)); } // keep only these overloads, the rest is made private @@ -827,14 +941,14 @@ public: return m_object.getField<T>(fieldName); } - template <typename T + template <typename Ret = void, typename T #ifndef Q_QDOC , QtJniTypes::IfValidFieldType<T> = true #endif > - void setField(const char *fieldName, T &&value) + auto setField(const char *fieldName, T &&value) { - m_object.setField(fieldName, std::forward<T>(value)); + return m_object.setField<Ret>(fieldName, std::forward<T>(value)); } QByteArray className() const { @@ -911,6 +1025,37 @@ struct Traits<QString> } }; +template <typename T> +struct Traits<T, std::enable_if_t<QtJniTypes::Detail::callerHandlesException<T>>> +{ + static constexpr auto className() + { + return Traits<typename T::value_type>::className(); + } + + static constexpr auto signature() + { + return Traits<typename T::value_type>::signature(); + } +}; + +} + +template <typename ReturnType, typename ...Args> +template <typename T> +auto QtJniTypes::Detail::LocalFrame<ReturnType, Args...>::convertFromJni(jobject object) +{ + // If the caller wants to handle exceptions through a std::expected-like + // type, then we cannot turn the jobject into a QJniObject, as a + // std::expected<jobject, jthrowable> cannot be constructed from a QJniObject. + // The caller will have to take care of this themselves, by asking for a + // std::expected<QJniObject, ...>, or (typically) using a declared JNI class + // or implicitly supported Qt type (QString or array type). + if constexpr (callerHandlesException<ReturnType> && + std::is_base_of_v<std::remove_pointer_t<jobject>, std::remove_pointer_t<T>>) + return static_cast<T>(object); + else + return convertFromJni<T>(object ? QJniObject::fromLocalRef(object) : QJniObject()); } QT_END_NAMESPACE diff --git a/src/corelib/kernel/qmetacontainer.cpp b/src/corelib/kernel/qmetacontainer.cpp index 4b4ea06d8b9..6173198a972 100644 --- a/src/corelib/kernel/qmetacontainer.cpp +++ b/src/corelib/kernel/qmetacontainer.cpp @@ -210,7 +210,7 @@ void QMetaContainer::destroyIterator(const void *iterator) const */ bool QMetaContainer::compareIterator(const void *i, const void *j) const { - return hasIterator() ? d_ptr->compareIteratorFn(i, j) : false; + return i == j || (hasIterator() && d_ptr->compareIteratorFn(i, j)); } /*! @@ -249,7 +249,7 @@ void QMetaContainer::advanceIterator(void *iterator, qsizetype step) const */ qsizetype QMetaContainer::diffIterator(const void *i, const void *j) const { - return hasIterator() ? d_ptr->diffIteratorFn(i, j) : 0; + return (i != j && hasIterator()) ? d_ptr->diffIteratorFn(i, j) : 0; } /*! @@ -327,7 +327,7 @@ void QMetaContainer::destroyConstIterator(const void *iterator) const */ bool QMetaContainer::compareConstIterator(const void *i, const void *j) const { - return hasConstIterator() ? d_ptr->compareConstIteratorFn(i, j) : false; + return i == j || (hasConstIterator() && d_ptr->compareConstIteratorFn(i, j)); } /*! @@ -366,7 +366,7 @@ void QMetaContainer::advanceConstIterator(void *iterator, qsizetype step) const */ qsizetype QMetaContainer::diffConstIterator(const void *i, const void *j) const { - return hasConstIterator() ? d_ptr->diffConstIteratorFn(i, j) : 0; + return (i != j && hasConstIterator()) ? d_ptr->diffConstIteratorFn(i, j) : 0; } QT_END_NAMESPACE diff --git a/src/corelib/kernel/qobject.cpp b/src/corelib/kernel/qobject.cpp index 02c9f00f301..607dc23f56c 100644 --- a/src/corelib/kernel/qobject.cpp +++ b/src/corelib/kernel/qobject.cpp @@ -2696,23 +2696,38 @@ static void err_method_notfound(const QObject *object, case QSIGNAL_CODE: type = "signal"; break; } const char *loc = extract_location(method); + const char *err; if (strchr(method, ')') == nullptr) // common typing mistake - qCWarning(lcConnect, "QObject::%s: Parentheses expected, %s %s::%s%s%s", func, type, - object->metaObject()->className(), method + 1, loc ? " in " : "", loc ? loc : ""); + err = "Parentheses expected,"; else - qCWarning(lcConnect, "QObject::%s: No such %s %s::%s%s%s", func, type, - object->metaObject()->className(), method + 1, loc ? " in " : "", loc ? loc : ""); + err = "No such"; + qCWarning(lcConnect, "QObject::%s: %s %s %s::%s%s%s", func, err, type, + object->metaObject()->className(), method + 1, loc ? " in " : "", loc ? loc : ""); +} + +enum class ConnectionEnd : bool { Sender, Receiver }; +Q_DECL_COLD_FUNCTION +static void err_info_about_object(const char *func, const QObject *o, ConnectionEnd end) +{ + if (!o) + return; + const QString name = o->objectName(); + if (name.isEmpty()) + return; + const bool sender = end == ConnectionEnd::Sender; + qCWarning(lcConnect, "QObject::%s: (%s name:%*s'%ls')", + func, + sender ? "sender" : "receiver", + sender ? 3 : 1, // ← length of generated whitespace + "", + qUtf16Printable(name)); } Q_DECL_COLD_FUNCTION static void err_info_about_objects(const char *func, const QObject *sender, const QObject *receiver) { - QString a = sender ? sender->objectName() : QString(); - QString b = receiver ? receiver->objectName() : QString(); - if (!a.isEmpty()) - qCWarning(lcConnect, "QObject::%s: (sender name: '%s')", func, a.toLocal8Bit().data()); - if (!b.isEmpty()) - qCWarning(lcConnect, "QObject::%s: (receiver name: '%s')", func, b.toLocal8Bit().data()); + err_info_about_object(func, sender, ConnectionEnd::Sender); + err_info_about_object(func, receiver, ConnectionEnd::Receiver); } Q_DECL_COLD_FUNCTION diff --git a/src/corelib/kernel/qpermissions.cpp b/src/corelib/kernel/qpermissions.cpp index bbcea8338ca..6767917e176 100644 --- a/src/corelib/kernel/qpermissions.cpp +++ b/src/corelib/kernel/qpermissions.cpp @@ -124,7 +124,11 @@ Q_LOGGING_CATEGORY(lcPermissions, "qt.permissions", QtWarningMsg); The relevant permission names are described in the documentation for each permission type. - \sa {Qt Creator: Editing Manifest Files} + \note When using this API, the \c{<!-- %%INSERT_PERMISSIONS -->} tag must be present in + the AndroidManifest.xml. For further information on the use of this tag, + see \l {Qt Permissions and Features} + + \sa {Qt Creator: Editing Manifest Files}. \section1 Available Permissions |
