Permission Manager

Add permission manager from Itinerary so that Android permissions can be checked.

Note at the moment the request permission functions are not hooked up so on Android the permission will need to be manually set on. I'll hook this up later but I wanted to confirm my suspicion on notifications being the current cause of crashes.
This commit is contained in:
James Graham
2024-10-01 17:29:39 +00:00
parent 45439e17c9
commit 644f5c0ce1
50 changed files with 3344 additions and 1 deletions

View File

@@ -39,6 +39,8 @@ include(ECMCheckOutboundLicense)
include(ECMQtDeclareLoggingCategory)
include(ECMAddAndroidApk)
include(ECMQmlModule)
include(GenerateExportHeader)
include(ECMGenerateHeaders)
if (NOT ANDROID)
include(KDEClangFormat)
endif()

View File

@@ -194,6 +194,8 @@ add_library(neochat STATIC
neochatroommember.h
models/threadmodel.cpp
models/threadmodel.h
permissionmanager.cpp
permissionmanager.h
)
set_source_files_properties(qml/OsmLocationPlugin.qml PROPERTIES
@@ -304,6 +306,10 @@ add_subdirectory(devtools)
add_subdirectory(login)
add_subdirectory(chatbar)
if (ANDROID)
add_subdirectory(kandroidextras)
endif()
if(NOT ANDROID AND NOT WIN32)
qt_target_qml_sources(neochat QML_FILES
qml/ShareAction.qml
@@ -436,7 +442,7 @@ endif()
if(ANDROID)
target_sources(neochat PRIVATE notifyrc.qrc)
target_link_libraries(neochat PRIVATE Qt::Svg OpenSSL::SSL)
target_link_libraries(neochat PUBLIC KAndroidExtras PRIVATE Qt::Svg OpenSSL::SSL)
if(SQLite3_FOUND)
target_link_libraries(neochat-app PRIVATE SQLite::SQLite3)
endif()

View File

@@ -0,0 +1,78 @@
# SPDX-FileCopyrightText: 2019 Volker Krause <vkrause@kde.org>
# SPDX-License-Identifier: BSD-3-Clause
add_library(KAndroidExtras STATIC)
target_sources(KAndroidExtras PRIVATE
android/activity.cpp
android/android_headers.cpp
android/contentresolver.cpp
android/context.cpp
android/intent.cpp
android/uri.cpp
java/java_headers.cpp
java/javalocale.cpp
jni/jni_headers.cpp
)
if (NOT ANDROID)
target_sources(KAndroidExtras PRIVATE
fake/mock_impl.cpp
fake/mock_jniobject.cpp
)
endif()
generate_export_header(KAndroidExtras BASE_NAME KAndroidExtras)
target_compile_features(KAndroidExtras PUBLIC cxx_std_20) # for __VA_OPT__
target_link_libraries(KAndroidExtras PUBLIC Qt::Core)
if (ANDROID)
target_link_libraries(KAndroidExtras PRIVATE Qt::CorePrivate)
else()
target_include_directories(KAndroidExtras PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/fake ${CMAKE_CURRENT_SOURCE_DIR}/fake/qt6)
endif()
ecm_generate_headers(KAndroidExtras_android_FORWARDING_HEADERS
HEADER_NAMES
Activity
AndroidTypes
CalendarContract
ContentResolver
Context
Intent
ManifestPermission
OpenableColumns
Settings
Uri
PREFIX KAndroidExtras
REQUIRED_HEADERS KAndroidExtras_android_HEADERS
RELATIVE android
)
ecm_generate_headers(KAndroidExtras_java_FORWARDING_HEADERS
HEADER_NAMES
JavaLocale
JavaTypes
PREFIX KAndroidExtras
REQUIRED_HEADERS KAndroidExtras_java_HEADERS
RELATIVE java
)
ecm_generate_headers(KAndroidExtras_jni_FORWARDING_HEADERS
HEADER_NAMES
JniArgument
JniArray
JniObject
JniMethod
JniReturnValue
JniPp
JniProperty
JniSignature
JniTypes
JniTypeTraits
PREFIX KAndroidExtras
REQUIRED_HEADERS KAndroidExtras_jni_HEADERS
RELATIVE jni
)

View File

@@ -0,0 +1,25 @@
# KAndroidExtras
Utilities for using Java Native Interface (JNI) to interface with Android platform API.
## Java Native Interface (JNI) wrapper
C++ header-only code for defining compile-time checked JNI wrappers.
Supported:
- typed `jobject` wrappers (`Jni::Object`)
- wrappers for Java arrays holding primitive or non-primitive content (`Jni::Array`)
- reading static final properties (`JNI_CONSTANT`)
- reading and writing non-static properties (`JNI_PROPERTY`)
- static and non-static method calls, constructors (`JNI_METHOD`, `JNI_STATIC_METHOD`, `JNI_CONSTRUCTOR`)
Not yet supported:
- registering native methods for Java -> C++ calls
## JNI mock implementation
This is useful for automated testing of JNI code on other platforms than Android.
## Wrappers for Java and Android types
Predefined wrappers for common platform types needed in multiple places.

View File

@@ -0,0 +1,2 @@
SPDX-FileCopyrightText: 2022 Volker Krause <vkrause@kde.org>
SPDX-License-Identifier: CC0-1.0

View File

@@ -0,0 +1,38 @@
/*
SPDX-FileCopyrightText: 2019 Volker Krause <vkrause@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "activity.h"
#include <KAndroidExtras/AndroidTypes>
#include <KAndroidExtras/Intent>
#include <KAndroidExtras/JniSignature>
#include "private/qandroidextras_p.h"
#include <QCoreApplication>
#include <QJniEnvironment>
using namespace KAndroidExtras;
Intent Activity::getIntent()
{
const QJniObject activity = QNativeInterface::QAndroidApplication::context();
if (!activity.isValid())
return {};
const auto intent = activity.callObjectMethod("getIntent", Jni::signature<android::content::Intent()>());
return Intent(Jni::fromHandle<Intent>(intent));
}
bool Activity::startActivity(const Intent &intent, int receiverRequestCode)
{
QJniEnvironment jniEnv;
QtAndroidPrivate::startActivity(intent, receiverRequestCode);
if (jniEnv->ExceptionCheck()) {
jniEnv->ExceptionClear();
return false;
}
return true;
}

View File

@@ -0,0 +1,29 @@
/*
SPDX-FileCopyrightText: 2019 Volker Krause <vkrause@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KANDROIDEXTRAS_ACTIVITY_H
#define KANDROIDEXTRAS_ACTIVITY_H
#include "kandroidextras_export.h"
namespace KAndroidExtras
{
class Intent;
/** Methods around android.app.Activity. */
namespace Activity
{
/** Returns the Intent that started the activity. */
KANDROIDEXTRAS_EXPORT Intent getIntent();
/** Same as QtAndroid::startActivity(), but with exception handling. */
KANDROIDEXTRAS_EXPORT bool startActivity(const Intent &intent, int receiverRequestCode); // TODO add callback arg
}
}
#endif // KANDROIDEXTRAS_ACTIVITY_H

View File

@@ -0,0 +1,13 @@
/*
SPDX-FileCopyrightText: 2020 Volker Krause <vkrause@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
// list all headers here that have no .cpp file
// this only serves as a guarantee that these headers actually compile
#include "androidtypes.h"
#include "calendarcontract.h"
#include "manifestpermission.h"
#include "openablecolumns.h"
#include "settings.h"

View File

@@ -0,0 +1,30 @@
/*
SPDX-FileCopyrightText: 2019 Volker Krause <vkrause@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KANDROIDEXTRAS_ANDROIDTYPES_H
#define KANDROIDEXTRAS_ANDROIDTYPES_H
#include <KAndroidExtras/JniTypes>
namespace KAndroidExtras
{
JNI_TYPE(android, content, ContentResolver)
JNI_TYPE(android, content, Context)
JNI_TYPE(android, content, Intent)
JNI_TYPE(android, database, Cursor)
JNI_NESTED_TYPE(android, Manifest, permission)
JNI_TYPE(android, net, Uri)
JNI_NESTED_TYPE(android, provider, CalendarContract, AttendeesColumns)
JNI_NESTED_TYPE(android, provider, CalendarContract, CalendarColumns)
JNI_NESTED_TYPE(android, provider, CalendarContract, EventsColumns)
JNI_NESTED_TYPE(android, provider, CalendarContract, RemindersColumns)
JNI_TYPE(android, provider, OpenableColumns)
JNI_TYPE(android, provider, Settings)
}
#endif // KANDROIDEXTRAS_ANDROIDTYPES_H

View File

@@ -0,0 +1,85 @@
/*
SPDX-FileCopyrightText: 2022 Volker Krause <vkrause@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KANDROIDEXTRAS_CALENDARCONTRACT_H
#define KANDROIDEXTRAS_CALENDARCONTRACT_H
#include <KAndroidExtras/AndroidTypes>
#include <KAndroidExtras/JavaTypes>
#include <KAndroidExtras/JniObject>
#include <KAndroidExtras/JniProperty>
namespace KAndroidExtras
{
/** CalendarContracts.EventColumns wrapper. */
class CalendarColumns
{
JNI_UNMANAGED_OBJECT(CalendarColumns, android::provider::CalendarContract_CalendarColumns)
JNI_CONSTANT(jint, CAL_ACCESS_CONTRIBUTOR)
JNI_CONSTANT(jint, CAL_ACCESS_EDITOR)
JNI_CONSTANT(jint, CAL_ACCESS_FREEBUSY)
JNI_CONSTANT(jint, CAL_ACCESS_NONE)
JNI_CONSTANT(jint, CAL_ACCESS_OVERRIDE)
JNI_CONSTANT(jint, CAL_ACCESS_OWNER)
JNI_CONSTANT(jint, CAL_ACCESS_READ)
JNI_CONSTANT(jint, CAL_ACCESS_RESPOND)
JNI_CONSTANT(jint, CAL_ACCESS_ROOT)
};
/** CalendarContracts.EventColumns wrapper. */
class EventsColumns
{
JNI_UNMANAGED_OBJECT(EventsColumns, android::provider::CalendarContract_EventsColumns)
JNI_CONSTANT(jint, ACCESS_CONFIDENTIAL)
JNI_CONSTANT(jint, ACCESS_DEFAULT)
JNI_CONSTANT(jint, ACCESS_PRIVATE)
JNI_CONSTANT(jint, ACCESS_PUBLIC)
JNI_CONSTANT(jint, AVAILABILITY_BUSY)
JNI_CONSTANT(jint, AVAILABILITY_FREE)
JNI_CONSTANT(jint, AVAILABILITY_TENTATIVE)
};
/** CalendarContracts.AttendeesColumns wrapper. */
class AttendeesColumns
{
JNI_UNMANAGED_OBJECT(AttendeesColumns, android::provider::CalendarContract_AttendeesColumns)
JNI_CONSTANT(jint, ATTENDEE_STATUS_ACCEPTED)
JNI_CONSTANT(jint, ATTENDEE_STATUS_DECLINED)
JNI_CONSTANT(jint, ATTENDEE_STATUS_INVITED)
JNI_CONSTANT(jint, ATTENDEE_STATUS_NONE)
JNI_CONSTANT(jint, ATTENDEE_STATUS_TENTATIVE)
JNI_CONSTANT(jint, RELATIONSHIP_ATTENDEE)
JNI_CONSTANT(jint, RELATIONSHIP_NONE)
JNI_CONSTANT(jint, RELATIONSHIP_ORGANIZER)
JNI_CONSTANT(jint, RELATIONSHIP_PERFORMER)
JNI_CONSTANT(jint, RELATIONSHIP_SPEAKER)
JNI_CONSTANT(jint, TYPE_NONE)
JNI_CONSTANT(jint, TYPE_OPTIONAL)
JNI_CONSTANT(jint, TYPE_REQUIRED)
JNI_CONSTANT(jint, TYPE_RESOURCE)
};
/** CalendarContract.RemindersColumns wrapper. */
class RemindersColumns
{
JNI_UNMANAGED_OBJECT(RemindersColumns, android::provider::CalendarContract_RemindersColumns)
JNI_CONSTANT(jint, METHOD_ALARM)
JNI_CONSTANT(jint, METHOD_ALERT)
JNI_CONSTANT(jint, METHOD_DEFAULT)
JNI_CONSTANT(jint, METHOD_EMAIL)
JNI_CONSTANT(jint, METHOD_SMS)
};
}
#endif // KANDROIDEXTRAS_OPENABLECOLUMNS_H

View File

@@ -0,0 +1,53 @@
/*
SPDX-FileCopyrightText: 2019 Volker Krause <vkrause@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "contentresolver.h"
#include "openablecolumns.h"
#include "uri.h"
#include <KAndroidExtras/AndroidTypes>
#include <KAndroidExtras/JniSignature>
#include <QCoreApplication>
#include <QString>
#include <QUrl>
using namespace KAndroidExtras;
QJniObject ContentResolver::get()
{
const QJniObject context = QNativeInterface::QAndroidApplication::context();
return context.callObjectMethod("getContentResolver", Jni::signature<android::content::ContentResolver()>());
}
QString ContentResolver::mimeType(const QUrl &url)
{
auto cs = ContentResolver::get();
const auto uri = Uri::fromUrl(url);
auto mt = cs.callObjectMethod("getType", Jni::signature<java::lang::String(android::net::Uri)>(), uri.object<jobject>());
return mt.toString();
}
QString ContentResolver::fileName(const QUrl &url)
{
auto cs = ContentResolver::get();
const auto uri = Uri::fromUrl(url);
// query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder)
auto cursor = cs.callObjectMethod(
"query",
Jni::signature<android::database::Cursor(android::net::Uri, java::lang::String[], java::lang::String, java::lang::String[], java::lang::String)>(),
uri.object<jobject>(),
0,
0,
0,
0);
const QJniObject DISPLAY_NAME = OpenableColumns::DISPLAY_NAME;
const auto nameIndex = cursor.callMethod<jint>("getColumnIndex", (const char *)Jni::signature<int(java::lang::String)>(), DISPLAY_NAME.object());
cursor.callMethod<jboolean>("moveToFirst", (const char *)Jni::signature<bool()>());
return cursor.callObjectMethod("getString", Jni::signature<java::lang::String(int)>(), nameIndex).toString();
}

View File

@@ -0,0 +1,37 @@
/*
SPDX-FileCopyrightText: 2019 Volker Krause <vkrause@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KANDROIDEXTRAS_CONTENTRESOLVER_H
#define KANDROIDEXTRAS_CONTENTRESOLVER_H
#include "kandroidextras_export.h"
#include <qglobal.h>
class QJniObject;
class QString;
class QUrl;
namespace KAndroidExtras
{
/** Methods for working with Android's ContentResolver. */
namespace ContentResolver
{
/** Get the JNI content resolver. */
KANDROIDEXTRAS_EXPORT QJniObject get();
/** Returns the mime type of the given content: URL.
* @see Android ContentResolver.getType
*/
KANDROIDEXTRAS_EXPORT QString mimeType(const QUrl &url);
/** File name of a file provided by a content: URL. */
KANDROIDEXTRAS_EXPORT QString fileName(const QUrl &url);
}
}
#endif // KANDROIDEXTRAS_CONTENTRESOLVER_H

View File

@@ -0,0 +1,20 @@
/*
SPDX-FileCopyrightText: 2020 Volker Krause <vkrause@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "context.h"
#include <KAndroidExtras/JavaTypes>
#include <KAndroidExtras/JniSignature>
#include <QCoreApplication>
using namespace KAndroidExtras;
QJniObject Context::getPackageName()
{
const QJniObject context = QNativeInterface::QAndroidApplication::context();
return context.callObjectMethod("getPackageName", Jni::signature<java::lang::String()>());
}

View File

@@ -0,0 +1,25 @@
/*
SPDX-FileCopyrightText: 2020 Volker Krause <vkrause@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KANDROIDEXTRAS_CONTEXT_H
#define KANDROIDEXTRAS_CONTEXT_H
#include "kandroidextras_export.h"
class QJniObject;
namespace KAndroidExtras
{
/** Methods around android.content.Context. */
namespace Context
{
KANDROIDEXTRAS_EXPORT QJniObject getPackageName();
}
}
#endif // KANDROIDEXTRAS_CONTEXT_H

View File

@@ -0,0 +1,39 @@
/*
SPDX-FileCopyrightText: 2019 Volker Krause <vkrause@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "intent.h"
#include "uri.h"
#include <KAndroidExtras/JniArray>
#include <KAndroidExtras/JniSignature>
#include <QUrl>
using namespace KAndroidExtras;
Intent::~Intent() = default;
Intent::operator QJniObject() const
{
return jniHandle();
}
template<typename T>
QJniObject Intent::getObjectExtra(const char *methodName, const QJniObject &name) const
{
return jniHandle().callObjectMethod(methodName, Jni::signature<T(java::lang::String)>(), name.object());
}
QString Intent::getStringExtra(const QJniObject &name) const
{
return getObjectExtra<java::lang::String>("getStringExtra", name).toString();
}
QStringList Intent::getStringArrayExtra(const QJniObject &name) const
{
const auto extra = getObjectExtra<Jni::Array<java::lang::String>>("getStringArrayExtra", name);
return Jni::fromArray<QStringList>(extra);
}

View File

@@ -0,0 +1,99 @@
/*
SPDX-FileCopyrightText: 2019 Volker Krause <vkrause@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KANDROIDEXTRAS_INTENT_H
#define KANDROIDEXTRAS_INTENT_H
#include "kandroidextras_export.h"
#include <KAndroidExtras/AndroidTypes>
#include <KAndroidExtras/JavaTypes>
#include <KAndroidExtras/JniMethod>
#include <KAndroidExtras/JniProperty>
#include <KAndroidExtras/Uri>
class QUrl;
namespace KAndroidExtras
{
/** Methods to interact with android.content.Intent objects.
* This does not only offer features beyond what QAndroidIntent, it also provides
* a putExtra() implementation that actually interoperates with system services.
*/
class KANDROIDEXTRAS_EXPORT Intent
{
JNI_OBJECT(Intent, android::content::Intent)
public:
/** Creates a new empty intent. */
JNI_CONSTRUCTOR(Intent)
~Intent();
/** Add a category to the intent. */
JNI_METHOD(android::content::Intent, addCategory, java::lang::String)
/** Add flags to this intent. */
JNI_METHOD(android::content::Intent, addFlags, jint)
/** Returns the data of this intent. */
JNI_METHOD(android::net::Uri, getData)
/** Get the intent action. */
JNI_METHOD(java::lang::String, getAction)
/** Sets the action of the intent. */
JNI_METHOD(android::content::Intent, setAction, java::lang::String)
/** Set the data URL of this intent. */
JNI_METHOD(android::content::Intent, setData, android::net::Uri)
/** Returns the mimetype of this intent. */
JNI_METHOD(java::lang::String, getType)
/** Set the mime type for this intent. */
JNI_METHOD(android::content::Intent, setType, java::lang::String)
/** Read extra intent data. */
QString getStringExtra(const QJniObject &name) const;
QStringList getStringArrayExtra(const QJniObject &name) const;
/** Add extra intent data of type @tparam T. */
template<typename T>
inline void putExtra(const QJniObject &name, const QJniObject &value)
{
jniHandle().callObjectMethod("putExtra", Jni::signature<android::content::Intent(java::lang::String, T)>(), name.object(), value.object());
}
/** Implicit conversion to an QJniObject. */
operator QJniObject() const;
/** Action constant for create document intents. */
JNI_CONSTANT(java::lang::String, ACTION_CREATE_DOCUMENT)
/** Main activity entry point. */
JNI_CONSTANT(java::lang::String, ACTION_MAIN)
/** Action constant for open document intents. */
JNI_CONSTANT(java::lang::String, ACTION_OPEN_DOCUMENT)
/** Action constant for viewing intents. */
JNI_CONSTANT(java::lang::String, ACTION_VIEW)
/** Share data. */
JNI_CONSTANT(java::lang::String, ACTION_SEND)
/** Share multiple data items. */
JNI_CONSTANT(java::lang::String, ACTION_SEND_MULTIPLE)
/** Category constant for openable content. */
JNI_CONSTANT(java::lang::String, CATEGORY_OPENABLE)
JNI_CONSTANT(java::lang::String, EXTRA_EMAIL)
JNI_CONSTANT(java::lang::String, EXTRA_STREAM)
JNI_CONSTANT(java::lang::String, EXTRA_SUBJECT)
JNI_CONSTANT(java::lang::String, EXTRA_TEXT)
/** Flag for granting read URI permissions on content providers. */
JNI_CONSTANT(jint, FLAG_GRANT_READ_URI_PERMISSION)
/** Flag for granting write URI permissions on content providers. */
JNI_CONSTANT(jint, FLAG_GRANT_WRITE_URI_PERMISSION)
private:
template<typename T>
QJniObject getObjectExtra(const char *methodName, const QJniObject &name) const;
};
}
#endif // KANDROIDEXTRAS_INTENT_H

View File

@@ -0,0 +1,36 @@
/*
SPDX-FileCopyrightText: 2020 Volker Krause <vkrause@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KANDROIDEXTRAS_MANIFESTPERMISSIONM_H
#define KANDROIDEXTRAS_MANIFESTPERMISSIONM_H
#include <KAndroidExtras/AndroidTypes>
#include <KAndroidExtras/JavaTypes>
#include <KAndroidExtras/JniObject>
#include <KAndroidExtras/JniProperty>
namespace KAndroidExtras
{
/**
* Access to manifest permissions.
* @see https://developer.android.com/reference/android/Manifest.permission
*/
class ManifestPermission
{
JNI_UNMANAGED_OBJECT(ManifestPermission, android::Manifest_permission)
public:
JNI_CONSTANT(java::lang::String, READ_CALENDAR)
JNI_CONSTANT(java::lang::String, WRITE_CALENDAR)
JNI_CONSTANT(java::lang::String, READ_EXTERNAL_STORAGE)
JNI_CONSTANT(java::lang::String, WRITE_EXTERNAL_STORAGE)
JNI_CONSTANT(java::lang::String, POST_NOTIFICATIONS)
JNI_CONSTANT(java::lang::String, CAMERA)
};
}
#endif // KANDROIDEXTRAS_MANIFESTPERMISSIONM_H

View File

@@ -0,0 +1,32 @@
/*
SPDX-FileCopyrightText: 2020 Volker Krause <vkrause@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KANDROIDEXTRAS_OPENABLECOLUMNS_H
#define KANDROIDEXTRAS_OPENABLECOLUMNS_H
#include <KAndroidExtras/AndroidTypes>
#include <KAndroidExtras/JavaTypes>
#include <KAndroidExtras/JniObject>
#include <KAndroidExtras/JniProperty>
namespace KAndroidExtras
{
/**
* Constants for ContentResolver queries.
* @see https://developer.android.com/reference/android/provider/OpenableColumns
*/
class OpenableColumns
{
JNI_UNMANAGED_OBJECT(OpenableColumns, android::provider::OpenableColumns)
public:
JNI_CONSTANT(java::lang::String, DISPLAY_NAME)
JNI_CONSTANT(java::lang::String, SIZE)
};
}
#endif // KANDROIDEXTRAS_OPENABLECOLUMNS_H

View File

@@ -0,0 +1,28 @@
/*
SPDX-FileCopyrightText: 2020 Volker Krause <vkrause@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KANDROIDEXTRAS_SETTINGS_H
#define KANDROIDEXTRAS_SETTINGS_H
#include <KAndroidExtras/AndroidTypes>
#include <KAndroidExtras/JniProperty>
namespace KAndroidExtras
{
/** Methods around android.provider.Settings. */
class Settings
{
JNI_UNMANAGED_OBJECT(Settings, android::provider::Settings)
JNI_CONSTANT(java::lang::String, ACTION_APP_NOTIFICATION_SETTINGS)
JNI_CONSTANT(java::lang::String, ACTION_CHANNEL_NOTIFICATION_SETTINGS)
JNI_CONSTANT(java::lang::String, EXTRA_APP_PACKAGE)
JNI_CONSTANT(java::lang::String, EXTRA_CHANNEL_ID)
};
}
#endif // KANDROIDEXTRAS_SETTINGS_H

View File

@@ -0,0 +1,28 @@
/*
SPDX-FileCopyrightText: 2019 Volker Krause <vkrause@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "uri.h"
#include <KAndroidExtras/JavaTypes>
#include <KAndroidExtras/JniSignature>
using namespace KAndroidExtras;
QJniObject Uri::fromUrl(const QUrl &url)
{
return QJniObject::callStaticObjectMethod(Jni::typeName<android::net::Uri>(),
"parse",
Jni::signature<android::net::Uri(java::lang::String)>(),
QJniObject::fromString(url.toString(QUrl::FullyEncoded)).object<jstring>());
}
QUrl Uri::toUrl(const QJniObject &uri)
{
if (!uri.isValid()) {
return QUrl();
}
return QUrl(uri.callObjectMethod("toString", Jni::signature<java::lang::String()>()).toString());
}

View File

@@ -0,0 +1,34 @@
/*
SPDX-FileCopyrightText: 2019 Volker Krause <vkrause@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KANDROIDEXTRAS_URI_H
#define KANDROIDEXTRAS_URI_H
#include "kandroidextras_export.h"
#include <KAndroidExtras/AndroidTypes>
#include <KAndroidExtras/JniTypeTraits>
#include <QUrl>
namespace KAndroidExtras
{
/** Conversion methods for android.net.Uri. */
namespace Uri
{
/** Create an android.net.Uri from a QUrl. */
KANDROIDEXTRAS_EXPORT QJniObject fromUrl(const QUrl &url);
/** Convert a android.net.Uri to a QUrl. */
KANDROIDEXTRAS_EXPORT QUrl toUrl(const QJniObject &uri);
}
JNI_DECLARE_CONVERTER(android::net::Uri, QUrl, (Uri::toUrl(value)), (Uri::fromUrl(value)))
}
#endif // KANDROIDEXTRAS_URI_H

View File

@@ -0,0 +1,208 @@
/*
SPDX-FileCopyrightText: 2020 Volker Krause <vkrause@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef FAKE_JNI_H
#define FAKE_JNI_H
#include <cstdint>
#include <numeric>
#ifdef Q_OS_ANDROID
#error This is a mock object for use on non-Android!
#endif
typedef uint8_t jboolean;
typedef int8_t jbyte;
typedef uint16_t jchar;
typedef int16_t jshort;
typedef int32_t jint;
typedef int64_t jlong;
typedef float jfloat;
typedef double jdouble;
typedef jint jsize;
typedef void *jobject;
typedef jobject jclass;
typedef jobject jstring;
typedef jobject jarray;
typedef jarray jobjectArray;
typedef jarray jbooleanArray;
typedef jarray jbyteArray;
typedef jarray jcharArray;
typedef jarray jshortArray;
typedef jarray jintArray;
typedef jarray jlongArray;
typedef jarray jfloatArray;
typedef jarray jdoubleArray;
namespace detail
{
template<typename T>
inline T *getArrayElements(jsize size)
{
T *array = new T[size];
std::iota(array, array + size, T{});
return array;
}
}
struct JNIEnv {
inline bool ExceptionCheck()
{
return false;
}
inline void ExceptionClear()
{
}
inline int GetArrayLength(jobjectArray)
{
return m_arrayLength;
}
inline jobjectArray NewObjectArray(jsize, jclass, jobject)
{
return nullptr;
}
inline jobject GetObjectArrayElement(jobjectArray, int index)
{
return reinterpret_cast<jobject>(index);
}
inline void SetObjectArrayElement(jobjectArray, jsize, jobject)
{
}
inline jbooleanArray NewBooleanArray(jsize)
{
return nullptr;
}
inline jbyteArray NewByteArray(jsize)
{
return nullptr;
}
inline jcharArray NewCharArray(jsize)
{
return nullptr;
}
inline jshortArray NewShortArray(jsize)
{
return nullptr;
}
inline jintArray NewIntArray(jsize)
{
return nullptr;
}
inline jlongArray NewLongArray(jsize)
{
return nullptr;
}
inline jfloatArray NewFloatArray(jsize)
{
return nullptr;
}
inline jdoubleArray NewDoubleArray(jsize)
{
return nullptr;
}
inline jboolean *GetBooleanArrayElements(jbooleanArray, jboolean *)
{
return detail::getArrayElements<jboolean>(m_arrayLength);
}
inline jbyte *GetByteArrayElements(jbyteArray, jboolean *)
{
return detail::getArrayElements<jbyte>(m_arrayLength);
}
inline jchar *GetCharArrayElements(jcharArray, jboolean *)
{
return detail::getArrayElements<jchar>(m_arrayLength);
}
inline jshort *GetShortArrayElements(jshortArray, jboolean *)
{
return detail::getArrayElements<jshort>(m_arrayLength);
}
inline jint *GetIntArrayElements(jintArray, jboolean *)
{
return detail::getArrayElements<jint>(m_arrayLength);
}
inline jlong *GetLongArrayElements(jlongArray, jboolean *)
{
return detail::getArrayElements<jlong>(m_arrayLength);
}
inline jfloat *GetFloatArrayElements(jfloatArray, jboolean *)
{
return detail::getArrayElements<jfloat>(m_arrayLength);
}
inline jdouble *GetDoubleArrayElements(jdoubleArray, jboolean *)
{
return detail::getArrayElements<jdouble>(m_arrayLength);
}
inline void ReleaseBooleanArrayElements(jbooleanArray, jboolean *data, jint)
{
delete[] data;
}
inline void ReleaseByteArrayElements(jbyteArray, jbyte *data, jint)
{
delete[] data;
}
inline void ReleaseCharArrayElements(jcharArray, jchar *data, jint)
{
delete[] data;
}
inline void ReleaseShortArrayElements(jshortArray, jshort *data, jint)
{
delete[] data;
}
inline void ReleaseIntArrayElements(jintArray, jint *data, jint)
{
delete[] data;
}
inline void ReleaseLongArrayElements(jlongArray, jlong *data, jint)
{
delete[] data;
}
inline void ReleaseFloatArrayElements(jfloatArray, jfloat *data, jint)
{
delete[] data;
}
inline void ReleaseDoubleArrayElements(jdoubleArray, jdouble *data, jint)
{
delete[] data;
}
inline void SetBooleanArrayRegion(jbooleanArray, jsize, jsize, const jboolean *)
{
}
inline void SetByteArrayRegion(jbyteArray, jsize, jsize, const jbyte *)
{
}
inline void SetCharArrayRegion(jcharArray, jsize, jsize, const jchar *)
{
}
inline void SetShortArrayRegion(jshortArray, jsize, jsize, const jshort *)
{
}
inline void SetIntArrayRegion(jintArray, jsize, jsize, const jint *)
{
}
inline void SetLongArrayRegion(jlongArray, jsize, jsize, const jlong *)
{
}
inline void SetFloatArrayRegion(jfloatArray, jsize, jsize, const jfloat *)
{
}
inline void SetDoubleArrayRegion(jdoubleArray, jsize, jsize, const jdouble *)
{
}
static jsize m_arrayLength;
};
#define JNI_COMMIT 1
#define JNI_ABORT 2
#endif

View File

@@ -0,0 +1,9 @@
/*
SPDX-FileCopyrightText: 2020 Volker Krause <vkrause@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "jni.h"
int JNIEnv::m_arrayLength = 0;

View File

@@ -0,0 +1,30 @@
/*
SPDX-FileCopyrightText: 2020-2022 Volker Krause <vkrause@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KANDROIDEXTRAS_MOCK_JNIENVIRONMENT_H
#define KANDROIDEXTRAS_MOCK_JNIENVIRONMENT_H
#include "jni.h"
namespace KAndroidExtras
{
class MockJniEnvironment
{
public:
inline jclass findClass(const char *)
{
return nullptr;
}
inline JNIEnv *operator->()
{
return &m_env;
}
protected:
mutable JNIEnv m_env;
};
}
#endif

View File

@@ -0,0 +1,80 @@
/*
SPDX-FileCopyrightText: 2020-2022 Volker Krause <vkrause@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "mock_jniobject.h"
using namespace KAndroidExtras::Internal;
QStringList MockJniObjectBase::m_staticProtocol;
class MockJniObjectBasePrivate : public QSharedData
{
public:
QStringList protocol;
QHash<QByteArray, QVariant> properties;
QVariant value;
};
MockJniObjectBase::MockJniObjectBase()
: d(new MockJniObjectBasePrivate)
{
}
MockJniObjectBase::MockJniObjectBase(const char *className)
: d(new MockJniObjectBasePrivate)
{
addToProtocol(QLatin1StringView("ctor: ") + QLatin1StringView(className));
}
MockJniObjectBase::MockJniObjectBase(jobject object)
: d(new MockJniObjectBasePrivate)
{
Q_UNUSED(object);
addToProtocol(QLatin1StringView("ctor: o"));
}
MockJniObjectBase::MockJniObjectBase(const MockJniObjectBase &) = default;
MockJniObjectBase &MockJniObjectBase::operator=(const MockJniObjectBase &) = default;
MockJniObjectBase::~MockJniObjectBase() = default;
QStringList MockJniObjectBase::protocol() const
{
return d->protocol;
}
void MockJniObjectBase::addToProtocol(const QString &line) const
{
d->protocol.push_back(line);
}
void MockJniObjectBase::setProtocol(const QStringList &protocol)
{
d->protocol = protocol;
}
QVariant MockJniObjectBase::property(const QByteArray &name) const
{
return d->properties.value(name);
}
void MockJniObjectBase::setProperty(const QByteArray &name, const QVariant &value)
{
d->properties.insert(name, value);
}
QVariant MockJniObjectBase::value() const
{
return d->value;
}
void MockJniObjectBase::setValue(const QVariant &value)
{
d->value = value;
}
void MockJniObjectBase::setData(jobject object)
{
d = reinterpret_cast<MockJniObjectBasePrivate *>(object);
}

View File

@@ -0,0 +1,262 @@
/*
SPDX-FileCopyrightText: 2020-2022 Volker Krause <vkrause@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KANDROIDEXTRAS_MOCK_JNIOBJECT_H
#define KANDROIDEXTRAS_MOCK_JNIOBJECT_H
#include "jni.h"
#include "kandroidextras_export.h"
#include "kandroidextras/jnisignature.h"
#include "kandroidextras/jnitypes.h"
#include <QExplicitlySharedDataPointer>
#include <QStringList>
#include <QVariant>
class MockJniObjectBasePrivate;
namespace KAndroidExtras
{
namespace Internal
{
template<typename T>
constexpr inline const char *argTypeToString()
{
return KAndroidExtras::Jni::signature<T>();
}
template<>
constexpr inline const char *argTypeToString<jobject>()
{
return "o";
}
class KANDROIDEXTRAS_EXPORT MockJniObjectBase
{
public:
MockJniObjectBase();
MockJniObjectBase(const MockJniObjectBase &);
~MockJniObjectBase();
MockJniObjectBase &operator=(const MockJniObjectBase &);
MockJniObjectBase(const char *className);
inline MockJniObjectBase(const char *className, const char *signature)
: MockJniObjectBase()
{
addToProtocol(QLatin1StringView("ctor: ") + QLatin1StringView(className) + QLatin1Char(' ') + QLatin1StringView(signature));
}
template<typename... Args>
inline MockJniObjectBase(const char *className, const char *signature, Args...)
: MockJniObjectBase()
{
addToProtocol(QLatin1StringView("ctor: ") + QLatin1StringView(className) + QLatin1Char(' ') + QLatin1StringView(signature) + QLatin1StringView(" (")
+ (... + QLatin1StringView(KAndroidExtras::Internal::argTypeToString<Args>())) + QLatin1Char(')'));
}
MockJniObjectBase(jobject object);
bool isValid() const
{
return true;
}
static QStringList m_staticProtocol;
QStringList protocol() const;
void addToProtocol(const QString &line) const;
protected:
QVariant property(const QByteArray &name) const;
void setProperty(const QByteArray &name, const QVariant &value);
QVariant value() const;
void setValue(const QVariant &value);
void setData(jobject object);
void setProtocol(const QStringList &protocol);
QExplicitlySharedDataPointer<MockJniObjectBasePrivate> d;
};
template<typename JniObjectT>
class MockJniObject : public MockJniObjectBase
{
public:
using MockJniObjectBase::MockJniObjectBase;
static inline JniObjectT fromString(const QString &s)
{
JniObjectT o;
o.setValue(s);
return o;
}
static inline JniObjectT fromLocalRef(jobject o)
{
JniObjectT obj;
obj.addToProtocol(QLatin1StringView("ctor: ") + QString::number((qulonglong)o));
return obj;
}
inline QString toString() const
{
return value().userType() == QMetaType::QString ? value().toString() : protocol().join(QLatin1Char('\n'));
}
inline jobject object() const
{
return d.data();
}
template<typename T>
inline T object() const
{
return {};
}
template<typename T, typename... Args>
inline T callMethod(const char *methodName, const char *signature, Args...) const
{
const QString s = QLatin1StringView("callMethod: ") + QLatin1StringView(methodName) + QLatin1Char(' ') + QLatin1StringView(signature)
+ QLatin1StringView(" (") + (... + QLatin1StringView(KAndroidExtras::Internal::argTypeToString<Args>())) + QLatin1Char(')');
addToProtocol(s);
if constexpr (!std::is_same_v<T, void>) {
return {};
}
}
template<typename T>
inline T callMethod(const char *methodName, const char *signature) const
{
const QString s =
QLatin1StringView("callMethod: ") + QLatin1StringView(methodName) + QLatin1Char(' ') + QLatin1StringView(signature) + QLatin1StringView(" ()");
addToProtocol(s);
if constexpr (!std::is_same_v<T, void>) {
return {};
}
}
template<typename... Args>
inline JniObjectT callObjectMethod(const char *methodName, const char *signature, Args...) const
{
const QString s = QLatin1StringView("callObjectMethod: ") + QLatin1StringView(methodName) + QLatin1Char(' ') + QLatin1StringView(signature)
+ QLatin1StringView(" (") + (... + QLatin1StringView(KAndroidExtras::Internal::argTypeToString<Args>())) + QLatin1Char(')');
addToProtocol(s);
JniObjectT obj;
obj.setProtocol(protocol());
return obj;
}
inline JniObjectT callObjectMethod(const char *methodName, const char *signature) const
{
addToProtocol(QLatin1StringView("callObjectMethod: ") + QLatin1StringView(methodName) + QLatin1Char(' ') + QLatin1StringView(signature)
+ QLatin1StringView(" ()"));
JniObjectT obj;
obj.setProtocol(protocol());
return obj;
}
template<typename T>
inline T getField(const char *fieldName) const
{
addToProtocol(QLatin1StringView("getField: ") + QLatin1StringView(fieldName) + QLatin1Char(' ')
+ QLatin1StringView(KAndroidExtras::Jni::signature<T>()));
return property(fieldName).template value<T>();
}
inline JniObjectT getObjectField(const char *fieldName, const char *signature) const
{
addToProtocol(QLatin1StringView("getObjectField: ") + QLatin1StringView(fieldName) + QLatin1Char(' ') + QLatin1StringView(signature));
return property(fieldName).template value<JniObjectT>();
}
template<typename T>
inline void setField(const char *fieldName, const char *signature, T value)
{
Q_UNUSED(value);
addToProtocol(QLatin1StringView("setField: ") + QLatin1StringView(fieldName) + QLatin1Char(' ') + QLatin1StringView(signature));
if constexpr (std::is_same_v<jobject, T>) {
setObjectProperty(fieldName, value);
} else {
setProperty(fieldName, QVariant::fromValue(value));
}
}
template<typename T>
inline void setField(const char *fieldName, T value)
{
setField(fieldName, KAndroidExtras::Jni::signature<T>(), value);
}
template<typename T, typename... Args>
static inline T callStaticMethod(const char *className, const char *methodName, const char *signature, Args...)
{
const QString s = QLatin1StringView("callStaticMethod: ") + QLatin1StringView(className) + QLatin1Char(' ') + QLatin1StringView(methodName)
+ QLatin1Char(' ') + QLatin1StringView(signature) + QLatin1StringView(" (")
+ (... + QLatin1StringView(KAndroidExtras::Internal::argTypeToString<Args>())) + QLatin1Char(')');
m_staticProtocol.push_back(s);
if constexpr (!std::is_same_v<T, void>) {
return {};
}
}
template<typename T>
static inline T callStaticMethod(const char *className, const char *methodName, const char *signature)
{
const QString s = QLatin1StringView("callStaticMethod: ") + QLatin1StringView(className) + QLatin1Char(' ') + QLatin1StringView(methodName)
+ QLatin1Char(' ') + QLatin1StringView(signature) + QLatin1StringView(" ()");
m_staticProtocol.push_back(s);
if constexpr (!std::is_same_v<T, void>) {
return {};
}
}
template<typename... Args>
static inline JniObjectT callStaticObjectMethod(const char *className, const char *methodName, const char *signature, Args...)
{
const QString s = QLatin1StringView("callStaticObjectMethod: ") + QLatin1StringView(className) + QLatin1Char(' ') + QLatin1StringView(methodName)
+ QLatin1Char(' ') + QLatin1StringView(signature) + QLatin1StringView(" (")
+ (... + QLatin1StringView(KAndroidExtras::Internal::argTypeToString<Args>())) + QLatin1Char(')');
JniObjectT obj;
obj.addToProtocol(s);
return obj;
}
static inline JniObjectT callStaticObjectMethod(const char *className, const char *methodName, const char *signature)
{
JniObjectT obj;
obj.addToProtocol(QLatin1StringView("callStaticObjectMethod: ") + QLatin1StringView(className) + QLatin1Char(' ') + QLatin1StringView(methodName)
+ QLatin1Char(' ') + QLatin1StringView(signature) + QLatin1StringView(" ()"));
return obj;
}
static inline JniObjectT getStaticObjectField(const char *className, const char *fieldName, const char *signature)
{
m_staticProtocol.push_back(QLatin1StringView("getStaticObjectField: ") + QLatin1StringView(className) + QLatin1Char(' ') + QLatin1StringView(fieldName)
+ QLatin1Char(' ') + QLatin1StringView(signature));
return {};
}
template<typename T>
static inline JniObjectT getStaticObjectField(const char *className, const char *fieldName)
{
m_staticProtocol.push_back(QLatin1StringView("getStaticObjectField<>: ") + QLatin1StringView(className) + QLatin1Char(' ')
+ QLatin1StringView(fieldName));
return {};
}
template<typename T>
static inline T getStaticField(const char *className, const char *fieldName)
{
m_staticProtocol.push_back(QLatin1StringView("getStaticField<>: ") + QLatin1StringView(className) + QLatin1Char(' ') + QLatin1StringView(fieldName)
+ QLatin1Char(' ') + QLatin1StringView(KAndroidExtras::Jni::signature<T>()));
return {};
}
inline void setObjectProperty(const QByteArray &name, jobject value)
{
JniObjectT o;
o.setData(value);
setProperty(name, QVariant::fromValue(o));
}
};
}
}
#endif

View File

@@ -0,0 +1,24 @@
/*
SPDX-FileCopyrightText: 2022 Volker Krause <vkrause@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KANDROIDEXTRAS_MOCK_QCOREAPPLICATION_H
#define KANDROIDEXTRAS_MOCK_QCOREAPPLICATION_H
#include_next <QCoreApplication>
#include <QJniObject>
namespace QNativeInterface {
namespace QAndroidApplication {
inline QJniObject context()
{
QJniObject obj;
obj.addToProtocol(QStringLiteral("global androidContext()"));
return obj;
}
}
}
#endif

View File

@@ -0,0 +1,17 @@
/*
SPDX-FileCopyrightText: 2020-2022 Volker Krause <vkrause@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KANDROIDEXTRAS_MOCK_QJNIENVIRONMENT_H
#define KANDROIDEXTRAS_MOCK_QJNIENVIRONMENT_H
#include "mock_jnienvironment.h"
class QJniEnvironment : public KAndroidExtras::MockJniEnvironment
{
public:
inline JNIEnv* jniEnv() const { return &m_env; }
};
#endif

View File

@@ -0,0 +1,6 @@
/*
SPDX-FileCopyrightText: 2022 Volker Krause <vkrause@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "qjniobject.h"

View File

@@ -0,0 +1,19 @@
/*
SPDX-FileCopyrightText: 2020-2022 Volker Krause <vkrause@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KANDROIDEXTRAS_MAKE_QTANDROIDEXTRAS_P_H
#define KANDROIDEXTRAS_MAKE_QTANDROIDEXTRAS_P_H
#include <QJniObject>
/** Mock object for QtAndroid namespace. */
namespace QtAndroidPrivate
{
inline void startActivity(const QJniObject &, int)
{
}
}
#endif

View File

@@ -0,0 +1,20 @@
/*
SPDX-FileCopyrightText: 2020-2022 Volker Krause <vkrause@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KANDROIDEXTRAS_MOCK_QJNIOBJECT_H
#define KANDROIDEXTRAS_MOCK_QJNIOBJECT_H
#include "mock_jniobject.h"
/** Mock object for QJniObject outside of Android, for automated testing. */
class KANDROIDEXTRAS_EXPORT QJniObject : public KAndroidExtras::Internal::MockJniObject<QJniObject>
{
public:
using MockJniObject<QJniObject>::MockJniObject;
};
Q_DECLARE_METATYPE(QJniObject)
#endif

View File

@@ -0,0 +1,9 @@
/*
SPDX-FileCopyrightText: 2020 Volker Krause <vkrause@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
// list all headers here that have no .cpp file
// this only serves as a guarantee that these headers actually compile
#include "javatypes.h"

View File

@@ -0,0 +1,32 @@
/*
SPDX-FileCopyrightText: 2019 Volker Krause <vkrause@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "javalocale.h"
#include <KAndroidExtras/JavaTypes>
#include <KAndroidExtras/JniSignature>
#include <QLocale>
using namespace KAndroidExtras;
QJniObject Locale::fromLocale(const QLocale &locale)
{
auto lang = QJniObject::fromString(QLocale::languageToString(locale.language()));
auto country = QJniObject::fromString(QLocale::countryToString(locale.country()));
auto script = QJniObject::fromString(QLocale::scriptToString(locale.script()));
return QJniObject(Jni::typeName<java::util::Locale>(),
(const char *)Jni::signature<void(java::lang::String, java::lang::String, java::lang::String)>(),
lang.object(),
country.object(),
script.object());
}
QJniObject Locale::current()
{
return fromLocale(QLocale());
}

View File

@@ -0,0 +1,33 @@
/*
SPDX-FileCopyrightText: 2019 Volker Krause <vkrause@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KANDROIDEXTRAS_LOCALE_H
#define KANDROIDEXTRAS_LOCALE_H
#include "kandroidextras_export.h"
class QJniObject;
class QLocale;
namespace KAndroidExtras
{
/** Conversion methods between java.util.Locale and QLocale.
* @note Do not rename this file to locale.h, that clashes with POSIX locale.h when your
* include paths are unfortunately set up causing bizarre compilation issues.
*/
namespace Locale
{
/** Create an java.util.Locale object from a QLocale. */
KANDROIDEXTRAS_EXPORT QJniObject fromLocale(const QLocale &locale);
/** Create an java.util.Locale object for the current QLocale. */
KANDROIDEXTRAS_EXPORT QJniObject current();
}
}
#endif // KANDROIDEXTRAS_LOCALE_H

View File

@@ -0,0 +1,24 @@
/*
SPDX-FileCopyrightText: 2019 Volker Krause <vkrause@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KANDROIDEXTRAS_JAVATYPES_H
#define KANDROIDEXTRAS_JAVATYPES_H
#include <KAndroidExtras/JniTypeTraits>
#include <KAndroidExtras/JniTypes>
namespace KAndroidExtras
{
JNI_TYPE(java, io, File)
JNI_TYPE(java, lang, String)
JNI_TYPE(java, util, Locale)
JNI_DECLARE_CONVERTER(java::lang::String, QString, (value.toString()), (QJniObject::fromString(value)))
}
#endif // KANDROIDEXTRAS_JAVATYPES_H

View File

@@ -0,0 +1,19 @@
/*
SPDX-FileCopyrightText: 2020 Volker Krause <vkrause@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
// list all headers here that have no .cpp file
// this only serves as a guarantee that these headers actually compile
#include "jniargument.h"
#include "jniarray.h"
#include "jnimethod.h"
#include "jniobject.h"
#include "jnipp.h"
#include "jniprimitivetypes.h"
#include "jniproperty.h"
#include "jnireturnvalue.h"
#include "jnisignature.h"
#include "jnitypes.h"
#include "jnitypetraits.h"

View File

@@ -0,0 +1,57 @@
/*
SPDX-FileCopyrightText: 2021-2022 Volker Krause <vkrause@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KANDROIDEXTRAS_JNIARGUMENTVALUE_H
#define KANDROIDEXTRAS_JNIARGUMENTVALUE_H
#include "jniobject.h"
#include "jnitypetraits.h"
namespace KAndroidExtras
{
namespace Jni
{
template<typename T>
class Array;
}
///@cond internal
namespace Internal
{
/** Call argument wrapper. */
template<typename T, typename = std::void_t<>>
struct argument {
static_assert(!is_invalid_primitive_type<T>::value, "Using an incompatible primitive type!");
typedef std::conditional_t<Jni::is_primitive_type<T>::value, T, const Jni::Object<T> &> type;
static inline constexpr auto toCallArgument(type value)
{
if constexpr (Jni::is_primitive_type<T>::value) {
return primitive_value<T>::toJni(value);
} else {
return value.jniHandle().object();
}
}
};
template<typename T>
struct argument<T, std::void_t<typename T::_jni_ThisType>> {
typedef const T &type;
static inline auto toCallArgument(const T &value)
{
return Jni::handle(value).object();
}
};
template<typename T>
struct argument<Jni::Array<T>> {
typedef const Jni::Array<T> &type;
static inline auto toCallArgument(const Jni::Array<T> &value)
{
return value.jniHandle().object();
}
};
}
///@endcond
}
#endif

View File

@@ -0,0 +1,395 @@
/*
SPDX-FileCopyrightText: 2020 Volker Krause <vkrause@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KANDROIDEXTRAS_JNIARRAY_H
#define KANDROIDEXTRAS_JNIARRAY_H
#include "jniargument.h"
#include "jnireturnvalue.h"
#include "jnitypetraits.h"
#include <QJniEnvironment>
namespace KAndroidExtras
{
///@cond internal
namespace Internal
{
/** Primitive type array type traits. */
template<typename T>
struct array_trait {
typedef jobjectArray type;
};
#define MAKE_ARRAY_TRAIT(base_type, type_name) \
template<> \
struct array_trait<base_type> { \
typedef base_type##Array type; \
static inline type newArray(QJniEnvironment &env, jsize size) \
{ \
return env->New##type_name##Array(size); \
} \
static inline base_type *getArrayElements(QJniEnvironment &env, type array, jboolean *isCopy) \
{ \
return env->Get##type_name##ArrayElements(array, isCopy); \
} \
static inline void releaseArrayElements(QJniEnvironment &env, type array, base_type *data, jint mode) \
{ \
return env->Release##type_name##ArrayElements(array, data, mode); \
} \
static inline void setArrayRegion(QJniEnvironment &env, type array, jsize start, jsize length, const base_type *data) \
{ \
env->Set##type_name##ArrayRegion(array, start, length, data); \
} \
};
MAKE_ARRAY_TRAIT(jboolean, Boolean)
MAKE_ARRAY_TRAIT(jbyte, Byte)
MAKE_ARRAY_TRAIT(jchar, Char)
MAKE_ARRAY_TRAIT(jshort, Short)
MAKE_ARRAY_TRAIT(jint, Int)
MAKE_ARRAY_TRAIT(jlong, Long)
MAKE_ARRAY_TRAIT(jfloat, Float)
MAKE_ARRAY_TRAIT(jdouble, Double)
#undef MAKE_ARRAY_TRAIT
/** Meta function for retrieving a JNI array .*/
template<typename Container, typename Value, bool is_primitive>
struct FromArray {
};
template<typename Container>
struct FromArray<Container, QJniObject, false> {
inline auto operator()(const QJniObject &array) const
{
if (!array.isValid()) {
return Container{};
}
const auto a = static_cast<jobjectArray>(array.object());
QJniEnvironment env;
const auto size = env->GetArrayLength(a);
Container r;
r.reserve(size);
for (auto i = 0; i < size; ++i) {
r.push_back(QJniObject::fromLocalRef(env->GetObjectArrayElement(a, i)));
}
return r;
}
};
template<typename Container, typename Value>
struct FromArray<Container, Value, false> {
inline auto operator()(const QJniObject &array) const
{
if (!array.isValid()) {
return Container{};
}
const auto a = static_cast<jobjectArray>(array.object());
QJniEnvironment env;
const auto size = env->GetArrayLength(a);
Container r;
r.reserve(size);
for (auto i = 0; i < size; ++i) {
r.push_back(Jni::reverse_converter<Value>::type::convert(QJniObject::fromLocalRef(env->GetObjectArrayElement(a, i))));
}
return r;
}
};
// specializations for primitive types
template<typename Container, typename Value>
struct FromArray<Container, Value, true> {
typedef array_trait<Value> _t;
inline auto operator()(const QJniObject &array) const
{
if (!array.isValid()) {
return Container{};
}
const auto a = static_cast<typename _t::type>(array.object());
QJniEnvironment env;
const auto size = env->GetArrayLength(a);
Container r;
r.reserve(size);
auto data = _t::getArrayElements(env, a, nullptr);
std::copy(data, data + size, std::back_inserter(r));
_t::releaseArrayElements(env, a, data, JNI_ABORT);
return r;
}
};
// array wrapper, common base for primitive and non-primitive types
template<typename T>
class ArrayImplBase
{
public:
typedef T value_type;
typedef jsize size_type;
typedef jsize difference_type;
ArrayImplBase() = default;
inline ArrayImplBase(const QJniObject &array)
: m_array(array)
{
}
ArrayImplBase(const ArrayImplBase &) = default;
ArrayImplBase(ArrayImplBase &&) = default;
inline size_type size() const
{
if (!m_array.isValid()) {
return 0;
}
const auto a = static_cast<typename _t::type>(m_array.object());
QJniEnvironment env;
return env->GetArrayLength(a);
}
inline operator QJniObject() const
{
return m_array;
}
inline QJniObject jniHandle() const
{
return m_array;
}
protected:
typedef array_trait<T> _t;
typename _t::type handle() const
{
return static_cast<typename _t::type>(m_array.object());
}
QJniObject m_array;
};
template<typename T, bool is_primitive>
class ArrayImpl
{
};
// array wrapper for primitive types
template<typename T>
class ArrayImpl<T, true> : public ArrayImplBase<T>
{
static_assert(!Internal::is_invalid_primitive_type<T>::value, "Using an incompatible primitive type!");
public:
inline ArrayImpl(const QJniObject &array)
: ArrayImplBase<T>(array)
{
// ### do this on demand?
getArrayElements();
}
/** Create a new array with @p size elements. */
inline explicit ArrayImpl(jsize size)
{
QJniEnvironment env;
ArrayImplBase<T>::m_array = QJniObject::fromLocalRef(ArrayImplBase<T>::_t::newArray(env, size));
getArrayElements();
}
ArrayImpl() = default;
ArrayImpl(const ArrayImpl &) = delete; // ### ref count m_data and allow copying?
ArrayImpl(ArrayImpl &&) = default;
~ArrayImpl()
{
QJniEnvironment env;
ArrayImplBase<T>::_t::releaseArrayElements(env, this->handle(), m_data, JNI_ABORT);
}
T operator[](jsize index) const
{
return m_data[index];
}
T *begin() const
{
return m_data;
}
T *end() const
{
return m_data + ArrayImplBase<T>::size();
}
private:
inline void getArrayElements()
{
if (!ArrayImplBase<T>::m_array.isValid()) {
return;
}
QJniEnvironment env;
m_data = ArrayImplBase<T>::_t::getArrayElements(env, this->handle(), nullptr);
}
T *m_data = nullptr;
};
// array wrapper for non-primitive types
template<typename T>
class ArrayImpl<T, false> : public ArrayImplBase<T>
{
public:
using ArrayImplBase<T>::ArrayImplBase;
/** Create a new array with @p size elements initialized with @p value. */
explicit inline ArrayImpl(jsize size, typename Internal::argument<T>::type value)
{
QJniEnvironment env;
auto clazz = env.findClass(Jni::typeName<T>());
ArrayImplBase<T>::m_array = QJniObject::fromLocalRef(env->NewObjectArray(size, clazz, Internal::argument<T>::toCallArgument(value)));
}
/** Create a new array with @p size null elements. */
explicit inline ArrayImpl(jsize size, std::nullptr_t = nullptr)
{
QJniEnvironment env;
auto clazz = env.findClass(Jni::typeName<T>());
ArrayImplBase<T>::m_array = QJniObject::fromLocalRef(env->NewObjectArray(size, clazz, nullptr));
}
ArrayImpl() = default;
ArrayImpl(const ArrayImpl &) = default;
ArrayImpl(ArrayImpl &&) = default;
auto operator[](jsize index) const
{
QJniEnvironment env;
return Internal::return_wrapper<T>::toReturnValue(QJniObject::fromLocalRef(env->GetObjectArrayElement(this->handle(), index)));
}
class ref
{
public:
inline operator auto()
{
QJniEnvironment env;
return Internal::return_wrapper<T>::toReturnValue(QJniObject::fromLocalRef(env->GetObjectArrayElement(c.handle(), index)));
}
inline ref &operator=(typename Internal::argument<T>::type v)
{
QJniEnvironment env;
env->SetObjectArrayElement(c.handle(), index, Internal::argument<T>::toCallArgument(v));
return *this;
}
private:
ArrayImpl<T, false> &c;
jsize index;
friend class ArrayImpl<T, false>;
inline ref(jsize _i, ArrayImpl<T, false> &_c)
: c(_c)
, index(_i)
{
}
};
ref operator[](jsize index)
{
return ref(index, *this);
}
class const_iterator
{
const ArrayImpl<T, false> &c;
jsize i = 0;
public:
typedef jsize difference_type;
typedef T value_type;
typedef T &reference;
typedef std::random_access_iterator_tag iterator_category;
typedef T *pointer;
const_iterator(const ArrayImpl<T, false> &_c, jsize _i)
: c(_c)
, i(_i)
{
}
difference_type operator-(const_iterator other) const
{
return i - other.i;
}
const_iterator &operator++()
{
++i;
return *this;
}
const_iterator operator++(int)
{
return const_iterator(c, i++);
}
bool operator==(const_iterator other) const
{
return i == other.i;
}
bool operator!=(const_iterator other) const
{
return i != other.i;
}
auto operator*() const
{
return c[i];
}
};
const_iterator begin() const
{
return const_iterator(*this, 0);
}
const_iterator end() const
{
return const_iterator(*this, ArrayImplBase<T>::size());
}
};
}
///@endcond
namespace Jni
{
/** Convert a JNI array to a C++ container.
* Container value types can be any of
* - QJniObject
* - a primitive JNI type
* - a type with a conversion defined with @c JNI_DECLARE_CONVERTER
*/
template<typename Container>
constexpr
__attribute__((__unused__)) Internal::FromArray<Container, typename Container::value_type, Jni::is_primitive_type<typename Container::value_type>::value>
fromArray = {};
/** Container-like wrapper for JNI arrays. */
template<typename T>
class Array : public Internal::ArrayImpl<T, Jni::is_primitive_type<T>::value>
{
public:
using Internal::ArrayImpl<T, Jni::is_primitive_type<T>::value>::ArrayImpl;
template<typename Container>
inline operator Container() const
{
// ### should this be re-implemented in terms of Jni::Array API rather than direct JNI access?
return Jni::fromArray<Container>(this->m_array);
}
};
}
}
#endif

View File

@@ -0,0 +1,192 @@
/*
SPDX-FileCopyrightText: 2021 Volker Krause <vkrause@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KANDROIDEXTRAS_JNIMETHOD_H
#define KANDROIDEXTRAS_JNIMETHOD_H
#include "jniargument.h"
#include "jniarray.h"
#include "jniobject.h"
#include "jnipp.h"
#include "jnireturnvalue.h"
#include "jnitypetraits.h"
namespace KAndroidExtras
{
///@cond internal
// method parameter generation
#define JNI_PARAM(Type, Name) typename KAndroidExtras::Internal::argument<Type>::type Name
#define JNI_PARAMS_0(accu, arg)
#define JNI_PARAMS_1(accu, arg, ...) JNI_PARAM(arg, a1)
#define JNI_PARAMS_2(accu, arg, ...) JNI_PARAM(arg, a2), JNI_PARAMS_1(accu, __VA_ARGS__)
#define JNI_PARAMS_3(accu, arg, ...) JNI_PARAM(arg, a3), JNI_PARAMS_2(accu, __VA_ARGS__)
#define JNI_PARAMS_(N, accu, ...) JNI_PP_CONCAT(JNI_PARAMS_, N)(accu, __VA_ARGS__)
#define JNI_PARAMS(...) JNI_PARAMS_(JNI_PP_NARG(__VA_ARGS__), "", __VA_ARGS__)
// method argument forwarding generation
#define JNI_ARG(Type, Name) KAndroidExtras::Internal::argument<Type>::toCallArgument(Name)
#define JNI_ARGS_0(accu, arg)
#define JNI_ARGS_1(accu, arg, ...) JNI_ARG(arg, a1)
#define JNI_ARGS_2(accu, arg, ...) JNI_ARG(arg, a2), JNI_ARGS_1(accu, __VA_ARGS__)
#define JNI_ARGS_3(accu, arg, ...) JNI_ARG(arg, a3), JNI_ARGS_2(accu, __VA_ARGS__)
#define JNI_ARGS_(N, accu, ...) JNI_PP_CONCAT(JNI_ARGS_, N)(accu, __VA_ARGS__)
#define JNI_ARGS(...) JNI_ARGS_(JNI_PP_NARG(__VA_ARGS__), "", __VA_ARGS__)
namespace Internal
{
// method call wrapper
template<typename RetT>
struct caller {
static_assert(!is_invalid_primitive_type<RetT>::value, "Using an incompatible primitive type!");
template<typename... Args>
static auto call(const QJniObject &handle, const char *name, const char *signature, Args &&...args)
{
if constexpr (std::is_same_v<RetT, void>) {
handle.callMethod<RetT>(name, signature, std::forward<Args>(args)...);
} else if constexpr (Jni::is_primitive_type<RetT>::value) {
return Internal::return_wrapper<RetT>::toReturnValue(
handle.callMethod<typename primitive_value<RetT>::JniType>(name, signature, std::forward<Args>(args)...));
} else {
return Internal::return_wrapper<RetT>::toReturnValue(handle.callObjectMethod(name, signature, std::forward<Args>(args)...));
}
}
static auto call(const QJniObject &handle, const char *name, const char *signature)
{
if constexpr (std::is_same_v<RetT, void>) {
return handle.callMethod<RetT>(name, signature);
} else if constexpr (Jni::is_primitive_type<RetT>::value) {
return Internal::return_wrapper<RetT>::toReturnValue(handle.callMethod<typename primitive_value<RetT>::JniType>(name, signature));
} else {
return Internal::return_wrapper<RetT>::toReturnValue(handle.callObjectMethod(name, signature));
}
}
};
// static method call wrapper
template<typename RetT>
struct static_caller {
static_assert(!is_invalid_primitive_type<RetT>::value, "Using an incompatible primitive type!");
template<typename... Args>
static auto call(const char *className, const char *name, const char *signature, Args &&...args)
{
if constexpr (std::is_same_v<RetT, void>) {
return QJniObject::callStaticMethod<RetT>(className, name, signature, std::forward<Args>(args)...);
} else if constexpr (Jni::is_primitive_type<RetT>::value) {
return Internal::return_wrapper<RetT>::toReturnValue(
QJniObject::callStaticMethod<typename primitive_value<RetT>::JniType>(className, name, signature, std::forward<Args>(args)...));
} else {
return Internal::return_wrapper<RetT>::toReturnValue(QJniObject::callStaticObjectMethod(className, name, signature, std::forward<Args>(args)...));
}
}
static auto call(const char *className, const char *name, const char *signature)
{
if constexpr (std::is_same_v<RetT, void>) {
return QJniObject::callStaticMethod<RetT>(className, name, signature);
} else if constexpr (Jni::is_primitive_type<RetT>::value) {
return Internal::return_wrapper<RetT>::toReturnValue(
QJniObject::callStaticMethod<typename primitive_value<RetT>::JniType>(className, name, signature));
} else {
return Internal::return_wrapper<RetT>::toReturnValue(QJniObject::callStaticObjectMethod(className, name, signature));
}
}
};
}
///@endcond
/**
* Wrap a JNI method call.
* This will add a method named @p Name to the current class. Argument types are checked at compile time,
* with the following inputs being accepted:
* - primitive types have to match exactly
* - non-primitive types can be either passed as @c QJniObject instance or with a type that has an
* conversion registered with @c JNI_DECLARE_CONVERTER.
*
* The return type of the method is determined as follows:
* - primitive types are returned directly
* - non-primitive types without a registered type conversion are returned as @c QJniObject.
* - non-primitive types with a registered type conversion are returned in a wrapper class that can
* be implicitly converted either to the destination type of the conversion, or a @c QJniObject.
* This allows to avoid type conversion when chaining calls for example, it however needs additional
* care when used in combination with automatic type deduction.
* - array return types also result in a wrapper class that can be implicitly converted to a sequential
* container or a @p QJniObject representing the JNI array.
*
* Thie macro can only be placed in classes having the @c JNI_OBJECT macro.
*
* @param RetT The return type. Must either be a primitive type or a type declared with @c JNI_TYPE
* @param Name The name of the method. Must match the JNI method to be called exactly.
* @param Args A list or argument types (can be empty). Must either be primitive types or types declared
* with @c JNI_TYPE.
*/
#define JNI_METHOD(RetT, Name, ...) \
inline auto Name(JNI_PARAMS(__VA_ARGS__)) const \
{ \
using namespace KAndroidExtras; \
return Internal::caller<RetT>::call(jniHandle(), "" #Name, Jni::signature<RetT(__VA_ARGS__)>() __VA_OPT__(, ) JNI_ARGS(__VA_ARGS__)); \
}
/**
* Wrap a JNI static method call.
* This will add a static method named @p Name to the current class. Argument types are checked at compile time,
* with the following inputs being accepted:
* - primitive types have to match exactly
* - non-primitive types can be either passed as @c QJniObject instance or with a type that has an
* conversion registered with @c JNI_DECLARE_CONVERTER.
*
* The return type of the method is determined as follows:
* - primitive types are returned directly
* - non-primitive types without a registered type conversion are returned as @c QJniObject.
* - non-primitive types with a registered type conversion are returned in a wrapper class that can
* be implicitly converted either to the destination type of the conversion, or a @c QJniObject.
* This allows to avoid type conversion when chaining calls for example, it however needs additional
* care when used in combination with automatic type deduction.
* - array return types also result in a wrapper class that can be implicitly converted to a sequential
* container or a @p QJniObject representing the JNI array.
*
* Thie macro can only be placed in classes having the @c JNI_UNMANAGED_OBJECT or @c JNI_OBJECT macro.
*
* @param RetT The return type. Must either be a primitive type or a type declared with @c JNI_TYPE
* @param Name The name of the method. Must match the JNI method to be called exactly.
* @param Args A list or argument types (can be empty). Must either be primitive types or types declared
* with @c JNI_TYPE.
*/
#define JNI_STATIC_METHOD(RetT, Name, ...) \
static inline auto Name(JNI_PARAMS(__VA_ARGS__)) \
{ \
using namespace KAndroidExtras; \
return Internal::static_caller<RetT>::call(Jni::typeName<_jni_ThisType>(), \
"" #Name, \
Jni::signature<RetT(__VA_ARGS__)>() __VA_OPT__(, ) JNI_ARGS(__VA_ARGS__)); \
}
/**
* Wrap a JNI constructor call.
* This will add a constructor named @p Name to the current class. Argument types are checked at compile time,
* with the following inputs being accepted:
* - primitive types have to match exactly
* - non-primitive types can be either passed as @c QJniObject instance or with a type that has an
* conversion registered with @c JNI_DECLARE_CONVERTER.
*
* Thie macro can only be placed in classes having @c JNI_OBJECT macro.
*
* @param Name The name of the method. Must match the JNI method to be called exactly.
* @param Args A list or argument types (can be empty). Must either be primitive types or types declared
* with @c JNI_TYPE.
*/
#define JNI_CONSTRUCTOR(Name, ...) \
inline Name(JNI_PARAMS(__VA_ARGS__)) \
{ \
using namespace KAndroidExtras; \
setJniHandle(QJniObject(Jni::typeName<_jni_ThisType>(), (const char *)Jni::signature<void(__VA_ARGS__)>() __VA_OPT__(, ) JNI_ARGS(__VA_ARGS__))); \
}
}
#endif

View File

@@ -0,0 +1,188 @@
/*
SPDX-FileCopyrightText: 2019-2021 Volker Krause <vkrause@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KANDROIDEXTRAS_JNIOBJECT_H
#define KANDROIDEXTRAS_JNIOBJECT_H
#include "jnitypetraits.h"
namespace KAndroidExtras
{
namespace java
{
namespace lang
{
struct String;
}
}
namespace Internal
{
struct FromHandleTag {
};
}
namespace Jni
{
template<typename T>
class Object;
template<typename T>
inline QJniObject handle(const T &wrapper)
{
return wrapper.jniHandle();
}
/** Convert an untyped JNI object handle to a typed wrapper.
* This is essentially the JNI equivalent to a reinterpret_cast from a void*.
* Only use this if you are sure @p handle has the correct type, otherwise
* things will horribly go wrong.
*/
template<typename T>
inline auto fromHandle(const QJniObject &handle)
{
if constexpr (Jni::is_object_wrapper<T>::value) {
return T(handle, Internal::FromHandleTag());
} else {
return Jni::Object<T>(handle);
}
}
template<typename T>
inline auto fromHandle(jobject handle)
{
return fromHandle<T>(QJniObject(handle));
}
/** Wrapper for JNI objects with a convertible C++ type.
* This provides implicit on-demand conversion to the C++ type, for types
* that don't have a manually defined wrapper.
*/
template<typename T>
class Object
{
private:
template<typename DummyT>
class _dummy_t
{
};
public:
inline explicit Object(const QJniObject &v)
: m_handle(v)
{
}
// implicit conversion from a compatible C++ type
inline Object(const std::conditional_t<std::is_same_v<typename Jni::converter<T>::type, void>, _dummy_t<T>, typename Jni::converter<T>::type> &v)
: m_handle(Jni::reverse_converter<T>::type::convert(v))
{
}
// implicit conversion from a custom wrapper for the same type
template<typename WrapperT, typename = std::enable_if_t<std::is_same_v<typename WrapperT::_jni_BaseType, T>, WrapperT>>
inline Object(const WrapperT &w)
: m_handle(Jni::handle(w))
{
}
// special-case QString, as that often comes out of implicitly converted types,
// and thus can't be consumed by another implicit conversion step
template<typename StrT = QString, typename = std::enable_if_t<std::is_same_v<T, java::lang::String>, StrT>>
inline Object(const QString &s)
: m_handle(QJniObject::fromString(s))
{
}
// special case for null values
inline Object(std::nullptr_t)
: m_handle((jobject) nullptr)
{
}
inline operator QJniObject() const
{
return m_handle;
}
inline operator typename Jni::converter<T>::type() const
{
return Jni::converter<T>::convert(m_handle);
}
inline QJniObject jniHandle() const
{
return m_handle;
}
// forward basic QJniObject API
inline bool isValid() const
{
return m_handle.isValid();
}
template<typename StrT = QString, typename = std::enable_if_t<std::is_same_v<T, java::lang::String>, StrT>>
inline QString toString() const
{
return m_handle.toString();
}
private:
QJniObject m_handle;
};
/** Annotates a class for holding JNI method or property wrappers.
*
* Use this if the class only has static methods or constants. For methods
* and properties, you either need to use @c JNI_OBJECT instead, or make
* sure there is a @p jniHandle() method returning a @c QJniObject
* representing the current instance.
*
* @param Class the name of the class this is added to.
* @param BaseType the Java type this class represents, defined by the
* @c JNI_TYPE or @c JNI_NESTED_TYPE macros.
*/
#define JNI_UNMANAGED_OBJECT(Class, BaseType) \
public: \
typedef Class _jni_ThisType; \
typedef BaseType _jni_BaseType; \
\
private: \
static inline constexpr const char *jniName() \
{ \
return KAndroidExtras::Jni::typeName<BaseType>(); \
} \
friend constexpr const char *KAndroidExtras::Jni::typeName<Class>();
/** Annotates a class for holding JNI method or property wrappers.
*
* @param Class the name of the class this is added to.
* @param BaseType the Java type this class represents, defined by the
* @c JNI_TYPE or @c JNI_NESTED_TYPE macros.
*/
#define JNI_OBJECT(Class, BaseType) \
JNI_UNMANAGED_OBJECT(Class, BaseType) \
private: \
QJniObject _m_jni_handle; \
inline QJniObject jniHandle() const \
{ \
return _m_jni_handle; \
} \
inline void setJniHandle(const QJniObject &h) \
{ \
_m_jni_handle = h; \
} \
friend QJniObject KAndroidExtras::Jni::handle<Class>(const Class &); \
friend auto KAndroidExtras::Jni::fromHandle<Class>(const QJniObject &); \
explicit inline Class(const QJniObject &handle, KAndroidExtras::Internal::FromHandleTag) \
: _m_jni_handle(handle) \
{ \
}
}
}
#endif

View File

@@ -0,0 +1,28 @@
/*
SPDX-FileCopyrightText: 2019-2022 Volker Krause <vkrause@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KANDROIDEXTRAS_JNIPP_H
#define KANDROIDEXTRAS_JNIPP_H
/** @file jnipp.h
* Preprocessor macro implementation details.
*/
///@cond internal
// determine how many elements are in __VA_ARGS__
#define JNI_PP_NARG(...) JNI_PP_NARG_(__VA_ARGS__ __VA_OPT__(, ) JNI_PP_RSEQ_N())
#define JNI_PP_NARG_(...) JNI_PP_ARG_N(__VA_ARGS__)
#define JNI_PP_ARG_N(_1, _2, _3, _4, _5, _6, _7, N, ...) N
#define JNI_PP_RSEQ_N() 7, 6, 5, 4, 3, 2, 1, 0
// preprocessor-level token concat
#define JNI_PP_CONCAT(arg1, arg2) JNI_PP_CONCAT1(arg1, arg2)
#define JNI_PP_CONCAT1(arg1, arg2) JNI_PP_CONCAT2(arg1, arg2)
#define JNI_PP_CONCAT2(arg1, arg2) arg1##arg2
///@endcond
#endif // KANDROIDEXTRAS_JNIPP_H

View File

@@ -0,0 +1,125 @@
/*
SPDX-FileCopyrightText: 2020-2022 Volker Krause <vkrause@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KANDROIDEXTRAS_JNIPRIMITIVETYPES_H
#define KANDROIDEXTRAS_JNIPRIMITIVETYPES_H
#include "jni.h"
#include <type_traits>
namespace KAndroidExtras
{
namespace Jni
{
/** Type trait to check whether @tparam T is a primitive JNI type or an object type. */
template<typename T>
struct is_primitive_type : std::false_type {
};
template<>
struct is_primitive_type<jboolean> : std::true_type {
};
template<>
struct is_primitive_type<jbyte> : std::true_type {
};
template<>
struct is_primitive_type<jchar> : std::true_type {
};
template<>
struct is_primitive_type<jshort> : std::true_type {
};
template<>
struct is_primitive_type<jint> : std::true_type {
};
template<>
struct is_primitive_type<jlong> : std::true_type {
};
template<>
struct is_primitive_type<jfloat> : std::true_type {
};
template<>
struct is_primitive_type<jdouble> : std::true_type {
};
template<>
struct is_primitive_type<void> : std::true_type {
};
// special case for bool <-> jboolean conversion
template<>
struct is_primitive_type<bool> : std::true_type {
};
}
namespace Internal
{
/** Utility trait to check for basic C++ types that are not JNI primitive types. */
template<typename T>
struct is_invalid_primitive_type : std::false_type {
};
template<>
struct is_invalid_primitive_type<uint8_t> : std::true_type {
};
template<>
struct is_invalid_primitive_type<char> : std::true_type {
};
template<>
struct is_invalid_primitive_type<uint32_t> : std::true_type {
};
template<>
struct is_invalid_primitive_type<uint64_t> : std::true_type {
};
// C++ primitive to JNI primitive conversion
template<typename T>
struct primitive_value {
static_assert(!is_invalid_primitive_type<T>::value, "Using an incompatible primitive type!");
typedef T JniType;
typedef T NativeType;
static constexpr inline T toJni(T value)
{
return value;
}
static constexpr inline T fromJni(T value)
{
return value;
}
};
template<>
struct primitive_value<void> {
};
template<>
struct primitive_value<jboolean> {
typedef jboolean JniType;
typedef bool NativeType;
static constexpr inline jboolean toJni(bool value)
{
return value ? 1 : 0;
}
static constexpr inline bool fromJni(jboolean value)
{
return value != 0;
}
};
template<>
struct primitive_value<bool> {
typedef jboolean JniType;
typedef bool NativeType;
static constexpr inline jboolean toJni(bool value)
{
return value ? 1 : 0;
}
static constexpr inline bool fromJni(jboolean value)
{
return value != 0;
}
};
}
}
#endif

View File

@@ -0,0 +1,227 @@
/*
SPDX-FileCopyrightText: 2020 Volker Krause <vkrause@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KANDROIDEXTRAS_JNIPROPERTIES_H
#define KANDROIDEXTRAS_JNIPROPERTIES_H
#include "jniargument.h"
#include "jniobject.h"
#include "jnisignature.h"
#include "jnitypes.h"
#include "jnitypetraits.h"
#include <type_traits>
namespace KAndroidExtras
{
namespace Jni
{
template<typename T>
class Array;
}
/** @cond internal */
namespace Internal
{
/** Wrapper for static properties. */
template<typename PropType, typename ClassType, typename NameHolder, bool PrimitiveType>
struct StaticProperty {
};
template<typename PropType, typename ClassType, typename NameHolder>
struct StaticProperty<PropType, ClassType, NameHolder, false> {
static_assert(!is_invalid_primitive_type<PropType>::value, "Using an incompatible primitive type!");
inline QJniObject get() const
{
return QJniObject::getStaticObjectField(Jni::typeName<ClassType>(), Jni::typeName<NameHolder>(), Jni::signature<PropType>());
}
inline operator QJniObject() const
{
return get();
}
inline operator typename Jni::converter<PropType>::type() const
{
return Jni::converter<PropType>::convert(get());
}
template<typename RetT = PropType, typename = std::enable_if_t<Jni::is_generic_wrapper<PropType>::value, RetT>>
inline operator Jni::Object<PropType>() const
{
return Jni::Object<PropType>(get());
}
};
template<typename PropType, typename ClassType, typename NameHolder>
struct StaticProperty<PropType, ClassType, NameHolder, true> {
inline operator auto() const
{
return primitive_value<PropType>::fromJni(
QJniObject::getStaticField<typename primitive_value<PropType>::JniType>(Jni::typeName<ClassType>(), Jni::typeName<NameHolder>()));
}
};
/** Shared code for non-static property wrappers. */
template<typename ClassType, typename OffsetHolder>
class PropertyBase
{
protected:
inline QJniObject handle() const
{
const auto owner = reinterpret_cast<const ClassType *>(reinterpret_cast<const char *>(this) - OffsetHolder::offset());
return owner->jniHandle();
}
};
/** Wrapper for non-static properties. */
template<typename PropType, typename ClassType, typename NameHolder, typename OffsetHolder, bool PrimitiveType>
struct Property {
};
template<typename PropType, typename ClassType, typename NameHolder, typename OffsetHolder>
class Property<PropType, ClassType, NameHolder, OffsetHolder, false> : public PropertyBase<ClassType, OffsetHolder>
{
private:
struct _jni_NoType {
};
static_assert(!is_invalid_primitive_type<PropType>::value, "Using an incompatible primitive type!");
public:
inline QJniObject get() const
{
return this->handle().getObjectField(Jni::typeName<NameHolder>(), Jni::signature<PropType>());
}
inline operator QJniObject() const
{
return get();
}
inline operator typename Jni::converter<PropType>::type() const
{
return Jni::converter<PropType>::convert(get());
}
template<typename RetT = PropType, typename = std::enable_if_t<Jni::is_array<PropType>::value, RetT>>
inline operator PropType() const
{
return PropType(get());
}
template<typename RetT = PropType, typename = std::enable_if_t<Jni::is_generic_wrapper<PropType>::value, RetT>>
inline operator Jni::Object<PropType>() const
{
return Jni::Object<PropType>(get());
}
inline Property &operator=(typename Internal::argument<PropType>::type value)
{
this->handle().setField(Jni::typeName<NameHolder>(), Jni::signature<PropType>(), Internal::argument<PropType>::toCallArgument(value));
return *this;
}
inline Property &operator=(const QJniObject &value)
{
this->handle().setField(Jni::typeName<NameHolder>(), Jni::signature<PropType>(), value.object());
return *this;
}
inline Property &operator=(const typename std::conditional<std::is_same_v<typename Jni::converter<PropType>::type, void>,
_jni_NoType,
typename Jni::converter<PropType>::type>::type &value)
{
this->handle().setField(Jni::typeName<NameHolder>(), Jni::signature<PropType>(), Jni::reverse_converter<PropType>::type::convert(value).object());
return *this;
}
// special case for string comparison, which is often done against different types and thus the implicit conversion operator
// isn't going to be enough
template<typename CmpT, typename = std::enable_if_t<std::is_same_v<PropType, java::lang::String>, CmpT>>
inline bool operator==(const CmpT &other) const
{
return QString(*this) == other;
}
};
template<typename PropType, typename ClassType, typename NameHolder, typename OffsetHolder>
class Property<PropType, ClassType, NameHolder, OffsetHolder, true> : public PropertyBase<ClassType, OffsetHolder>
{
public:
inline operator auto() const
{
return primitive_value<PropType>::fromJni(this->handle().template getField<typename primitive_value<PropType>::JniType>(Jni::typeName<NameHolder>()));
}
inline Property &operator=(PropType value)
{
this->handle().setField(Jni::typeName<NameHolder>(), primitive_value<PropType>::toJni(value));
return *this;
}
};
// TODO KF6: can be replaced by QT_WARNING_DISABLE_INVALID_OFFSETOF
#if defined(Q_CC_CLANG)
#define JNI_WARNING_DISABLE_INVALID_OFFSETOF QT_WARNING_DISABLE_CLANG("-Winvalid-offsetof")
#elif defined(Q_CC_GNU)
#define JNI_WARNING_DISABLE_INVALID_OFFSETOF QT_WARNING_DISABLE_GCC("-Winvalid-offsetof")
#else
#define JNI_WARNING_DISABLE_INVALID_OFFSETOF
#endif
/** @endcond */
}
/**
* Wrap a static final property.
* This will add a public static member named @p name to the current class. This member defines an
* implicit conversion operator which will trigger the corresponding a JNI read operation.
* Can only be placed in classes with a @c JNI_OBJECT.
*
* @note Make sure to access this member with a specific type, assigning to an @c auto variable will
* copy the wrapper type, not read the property value.
*
* @param type The data type of the property.
* @param name The name of the property.
*/
#define JNI_CONSTANT(type, name) \
private: \
struct _jni_##name##__NameHolder { \
static constexpr const char *jniName() \
{ \
return "" #name; \
} \
}; \
\
public: \
static inline const KAndroidExtras::Internal::StaticProperty<type, _jni_ThisType, _jni_##name##__NameHolder, Jni::is_primitive_type<type>::value> name;
/**
* Wrap a member property.
* This will add a public zero-size member named @p name to the current class. This member defines an
* implicit conversion operator which will trigger the corresponding a JNI read operation, as well
* as an overloaded assignment operator for the corresponding write operation.
* Can only be placed in classes with a @c JNI_OBJECT.
*
* @note Make sure to access this member with a specific type, assigning to an @c auto variable will
* copy the wrapper type, not read the property value.
*
* @param type The data type of the property.
* @param name The name of the property.
*/
#define JNI_PROPERTY(type, name) \
private: \
struct _jni_##name##__NameHolder { \
static constexpr const char *jniName() \
{ \
return "" #name; \
} \
}; \
struct _jni_##name##__OffsetHolder { \
static constexpr std::size_t offset() \
{ \
QT_WARNING_PUSH JNI_WARNING_DISABLE_INVALID_OFFSETOF return offsetof(_jni_ThisType, name); \
QT_WARNING_POP \
} \
}; \
friend class KAndroidExtras::Internal::PropertyBase<_jni_ThisType, _jni_##name##__OffsetHolder>; \
\
public: \
[[no_unique_address]] KAndroidExtras::Internal:: \
Property<type, _jni_ThisType, _jni_##name##__NameHolder, _jni_##name##__OffsetHolder, KAndroidExtras::Jni::is_primitive_type<type>::value> name;
}
#endif // KANDROIDEXTRAS_JNIPROPERTIES_H

View File

@@ -0,0 +1,70 @@
/*
SPDX-FileCopyrightText: 2021-2022 Volker Krause <vkrause@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KANDROIDEXTRAS_JNIRETURNVALUE_H
#define KANDROIDEXTRAS_JNIRETURNVALUE_H
#include "jniobject.h"
#include "jnitypetraits.h"
namespace KAndroidExtras
{
namespace Jni
{
template<typename T>
class Array;
}
///@cond internal
namespace Internal
{
/** Return value wrapping helper. */
template<typename RetT, typename = std::void_t<>>
class return_wrapper
{
static_assert(!is_invalid_primitive_type<RetT>::value, "Using an incompatible primitive type!");
static inline constexpr bool is_primitive = Jni::is_primitive_type<RetT>::value;
static inline constexpr bool is_convertible = !std::is_same_v<typename Jni::converter<RetT>::type, void>;
typedef std::conditional_t<is_primitive, RetT, QJniObject> JniReturnT;
public:
static inline constexpr auto toReturnValue(JniReturnT value)
{
if constexpr (is_convertible) {
return Jni::Object<RetT>(value);
} else if constexpr (is_primitive) {
return primitive_value<RetT>::fromJni(value);
} else {
return value;
}
}
};
template<typename RetT>
class return_wrapper<RetT, std::void_t<typename RetT::_jni_ThisType>>
{
public:
static inline auto toReturnValue(const QJniObject &value)
{
return Jni::fromHandle<RetT>(value);
}
};
template<typename RetT>
class return_wrapper<Jni::Array<RetT>>
{
public:
static inline auto toReturnValue(const QJniObject &value)
{
return Jni::Array<RetT>(value);
}
};
template<>
struct return_wrapper<void> {
};
}
///@endcond
}
#endif

View File

@@ -0,0 +1,190 @@
/*
SPDX-FileCopyrightText: 2019 Volker Krause <vkrause@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KANDROIDEXTRAS_JNISIGNATURE_H
#define KANDROIDEXTRAS_JNISIGNATURE_H
#include "jnitypes.h"
#include "jni.h"
#include <cstdint>
#include <utility>
namespace KAndroidExtras
{
namespace Jni
{
template<typename T>
class Array;
}
/** @cond internal */
namespace Internal
{
/** Compile-time concat-able string. */
template<char... String>
struct StaticString {
inline operator const char *() const
{
static const char data[] = {String..., 0};
return data;
}
};
/** Compile-time strlen. */
constexpr inline int static_strlen(const char *str)
{
return str[0] == '\0' ? 0 : static_strlen(str + 1) + 1;
}
/** Compile-time concat for two StaticString. */
template<char... String1, char... String2>
constexpr inline auto static_concat(const StaticString<String1...> &, const StaticString<String2...> &)
{
return StaticString<String1..., String2...>();
}
/** Compile-time concept for N StaticString. */
template<typename String1, typename String2, class... Strings>
constexpr inline auto static_concat(const String1 &str1, const String2 &str2, const Strings &...strs)
{
return static_concat(static_concat(str1, str2), strs...);
}
/** Conversion from const char* literals to StaticString. */
template<typename, typename>
struct staticStringFromJniType;
template<typename T, std::size_t... Indexes>
struct staticStringFromJniType<T, std::index_sequence<Indexes...>> {
typedef StaticString<Jni::typeName<T>()[Indexes]...> value;
};
/** Meta function for assembling JNI signatures. */
template<typename T>
struct JniSignature {
constexpr inline auto operator()() const
{
using namespace Internal;
return static_concat(StaticString<'L'>(),
typename staticStringFromJniType<T, std::make_index_sequence<static_strlen(Jni::typeName<T>())>>::value(),
StaticString<';'>());
}
};
template<>
struct JniSignature<jboolean> {
constexpr inline auto operator()() const
{
return StaticString<'Z'>();
}
};
template<>
struct JniSignature<jbyte> {
constexpr inline auto operator()() const
{
return StaticString<'B'>();
}
};
template<>
struct JniSignature<jchar> {
constexpr inline auto operator()() const
{
return StaticString<'C'>();
}
};
template<>
struct JniSignature<jshort> {
constexpr inline auto operator()() const
{
return StaticString<'S'>();
}
};
template<>
struct JniSignature<jint> {
constexpr inline auto operator()() const
{
return StaticString<'I'>();
}
};
template<>
struct JniSignature<jlong> {
constexpr inline auto operator()() const
{
return StaticString<'J'>();
}
};
template<>
struct JniSignature<jfloat> {
constexpr inline auto operator()() const
{
return StaticString<'F'>();
}
};
template<>
struct JniSignature<jdouble> {
constexpr inline auto operator()() const
{
return StaticString<'D'>();
}
};
template<>
struct JniSignature<void> {
constexpr inline auto operator()() const
{
return StaticString<'V'>();
}
};
// special case due to jboolean != bool
template<>
struct JniSignature<bool> {
constexpr inline auto operator()() const
{
return StaticString<'Z'>();
}
};
template<typename T>
struct JniSignature<T *> {
constexpr inline auto operator()() const
{
using namespace Internal;
return static_concat(StaticString<'['>(), JniSignature<T>()());
}
};
template<typename T>
struct JniSignature<Jni::Array<T>> {
constexpr inline auto operator()() const
{
using namespace Internal;
return static_concat(StaticString<'['>(), JniSignature<T>()());
}
};
template<typename RetT, typename... Args>
struct JniSignature<RetT(Args...)> {
constexpr inline auto operator()() const
{
using namespace Internal;
return static_concat(StaticString<'('>(), JniSignature<Args>()()..., StaticString<')'>(), JniSignature<RetT>()());
}
};
}
/** @endcond */
/** Helper methods to deal with JNI. */
namespace Jni
{
/** Returns the JNI signature string for the template argument types. */
template<typename... Args>
constexpr __attribute__((__unused__)) Internal::JniSignature<Args...> signature = {};
}
}
#endif // KANDROIDEXTRAS_JNISIGNATURE_H

View File

@@ -0,0 +1,114 @@
/*
SPDX-FileCopyrightText: 2019 Volker Krause <vkrause@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KANDROIDEXTRAS_JNITYPES_H
#define KANDROIDEXTRAS_JNITYPES_H
#include "jnipp.h"
/** C++/Android integration utilities built on top of @c QJniObject. */
namespace KAndroidExtras
{
///@cond internal
// preprocessor "iteration" for regular classes
#define JNI_TYPE_1(name, type, ...) \
struct type { \
static constexpr const char *jniName() \
{ \
return name #type; \
} \
};
#define JNI_TYPE_2(name, type, ...) \
namespace type \
{ \
JNI_TYPE_1(name #type "/", __VA_ARGS__) \
}
#define JNI_TYPE_3(name, type, ...) \
namespace type \
{ \
JNI_TYPE_2(name #type "/", __VA_ARGS__) \
}
#define JNI_TYPE_4(name, type, ...) \
namespace type \
{ \
JNI_TYPE_3(name #type "/", __VA_ARGS__) \
}
#define JNI_TYPE_5(name, type, ...) \
namespace type \
{ \
JNI_TYPE_4(name #type "/", __VA_ARGS__) \
}
#define JNI_TYPE_6(name, type, ...) \
namespace type \
{ \
JNI_TYPE_5(name #type "/", __VA_ARGS__) \
}
#define JNI_TYPE_7(name, type, ...) \
namespace type \
{ \
JNI_TYPE_6(#type "/", __VA_ARGS__) \
}
#define JNI_TYPE_(N, name, ...) JNI_PP_CONCAT(JNI_TYPE_, N)(name, __VA_ARGS__)
// preprocessor "iteration" for nested classes
#define JNI_NESTED_TYPE_2(name, type, nested_type, ...) \
struct type##_##nested_type { \
static constexpr const char *jniName() \
{ \
return name #type "$" #nested_type; \
} \
};
#define JNI_NESTED_TYPE_3(name, type, ...) \
namespace type \
{ \
JNI_NESTED_TYPE_2(name #type "/", __VA_ARGS__) \
}
#define JNI_NESTED_TYPE_4(name, type, ...) \
namespace type \
{ \
JNI_NESTED_TYPE_3(name #type "/", __VA_ARGS__) \
}
#define JNI_NESTED_TYPE_5(name, type, ...) \
namespace type \
{ \
JNI_NESTED_TYPE_4(name #type "/", __VA_ARGS__) \
}
#define JNI_NESTED_TYPE_6(name, type, ...) \
namespace type \
{ \
JNI_NESTED_TYPE_5(name #type "/", __VA_ARGS__) \
}
#define JNI_NESTED_TYPE_7(name, type, ...) \
namespace type \
{ \
JNI_NESTED_TYPE_6(#type "/", __VA_ARGS__) \
}
#define JNI_NESTED_TYPE_(N, name, ...) JNI_PP_CONCAT(JNI_NESTED_TYPE_, N)(name, __VA_ARGS__)
///@endcond
/** Macro to define Java types with their corresponding JNI signature strings. */
#define JNI_TYPE(...) JNI_TYPE_(JNI_PP_NARG(__VA_ARGS__), "", __VA_ARGS__)
/** Macro to define a nested Java class with its corresponding JNI signature string. */
#define JNI_NESTED_TYPE(...) JNI_NESTED_TYPE_(JNI_PP_NARG(__VA_ARGS__), "", __VA_ARGS__)
/** Functions for interfacing with the Java Native Interface (JNI). */
namespace Jni
{
/** Returns the JNI type name of the given template argument. */
template<typename T>
inline constexpr const char *typeName()
{
return T::jniName();
}
}
}
#endif // KANDROIDEXTRAS_JNITYPES_H

View File

@@ -0,0 +1,89 @@
/*
SPDX-FileCopyrightText: 2020 Volker Krause <vkrause@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KANDROIDEXTRAS_JNITYPETRAITS_H
#define KANDROIDEXTRAS_JNITYPETRAITS_H
#include "jniprimitivetypes.h"
#include "jnitypes.h"
#include <QJniObject>
namespace KAndroidExtras
{
namespace Jni
{
template<typename T>
class Array;
/** Type conversion trait, see @c JNI_DECLARE_CONVERTER. */
template<typename T>
struct converter {
typedef void type;
};
template<typename T>
struct reverse_converter {
typedef converter<typename converter<T>::type> type;
};
/** Type trait for checking whether @tparam T is a JNI array type. */
template<typename T>
struct is_array : std::false_type {
};
template<typename T>
struct is_array<Array<T>> : std::true_type {
};
/** Type trais for checking whether @tparam T is a JNI object wrapper type. */
template<typename T, typename = std::void_t<>>
struct is_object_wrapper : std::false_type {
};
template<typename T>
struct is_object_wrapper<T, std::void_t<typename T::_jni_ThisType>> : std::true_type {
};
/** Type trais for checking whether @tparam T is needs the generic JNI object wrapper (Jni::Object). */
template<typename T>
struct is_generic_wrapper
: std::conditional_t<!is_primitive_type<T>::value && !is_array<T>::value && !is_object_wrapper<T>::value, std::true_type, std::false_type> {
};
}
/**
* Declare a JNI type to be convertible to a native type.
* @param JniType A type declared with @p JNI_TYPE.
* @param NativeType A C++ type @p JniType can be converted to/from.
* @param FromJniFn Code converting a @c QJniObject @c value to @p NativeType.
* @param ToJniCode converting a @p NativeType @c value to a QJniObject.
*/
#define JNI_DECLARE_CONVERTER(JniType, NativeType, FromJniFn, ToJniFn) \
namespace Jni \
{ \
template<> \
struct converter<NativeType> { \
typedef JniType type; \
static inline QJniObject convert(const NativeType &value) \
{ \
return (ToJniFn); \
} \
}; \
template<> \
struct converter<JniType> { \
typedef NativeType type; \
static inline NativeType convert(const QJniObject &value) \
{ \
return (FromJniFn); \
} \
}; \
}
}
#endif

View File

@@ -23,6 +23,7 @@
#include "controller.h"
#include "neochatconnection.h"
#include "neochatroom.h"
#include "permissionmanager.h"
#include "roommanager.h"
#include "texthandler.h"
#include "windowcontroller.h"
@@ -42,6 +43,10 @@ NotificationsManager::NotificationsManager(QObject *parent)
void NotificationsManager::handleNotifications(QPointer<NeoChatConnection> connection)
{
if (!PermissionManager::instance().checkPermission(Permission::PostNotification)) {
return;
}
if (!m_connActiveJob.contains(connection->user()->id())) {
auto job = connection->callApi<GetNotificationsJob>();
m_connActiveJob.append(connection->user()->id());

88
src/permissionmanager.cpp Normal file
View File

@@ -0,0 +1,88 @@
/*
SPDX-FileCopyrightText: 2022 Volker Krause <vkrause@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "permissionmanager.h"
#include <QDebug>
#ifdef Q_OS_ANDROID
#include "private/qandroidextras_p.h"
#include <KAndroidExtras/ManifestPermission>
#endif
#ifdef Q_OS_ANDROID
static QString permissionName(Permission::Permission p)
{
using namespace KAndroidExtras;
switch (p) {
case Permission::InvalidPermission:
Q_UNREACHABLE();
case Permission::ReadCalendar:
return ManifestPermission::READ_CALENDAR;
case Permission::WriteCalendar:
return ManifestPermission::WRITE_CALENDAR;
case Permission::PostNotification: // only for SDK >= 33
return ManifestPermission::POST_NOTIFICATIONS;
case Permission::Camera:
return ManifestPermission::CAMERA;
}
}
#endif
PermissionManager &PermissionManager::instance()
{
static PermissionManager _instance;
return _instance;
}
PermissionManager::PermissionManager(QObject *parent)
: QObject(parent)
{
}
bool PermissionManager::checkPermission(Permission::Permission permission)
{
if (permission == Permission::InvalidPermission) {
qWarning() << "check for invalid permission - check your QML code!";
return false;
}
#ifdef Q_OS_ANDROID
if (permission == Permission::PostNotification && QtAndroidPrivate::androidSdkVersion() < 33) {
return true;
}
return QtAndroidPrivate::checkPermission(permissionName(permission)).result() == QtAndroidPrivate::PermissionResult::Authorized;
#else // non-Android
Q_UNUSED(permission);
return true;
#endif
}
void PermissionManager::requestPermission(Permission::Permission permission, QJSValue callback)
{
if (permission == Permission::InvalidPermission) {
qWarning() << "request for invalid permission - check your QML code!";
return;
}
qDebug() << permission;
// already got the permission
if (PermissionManager::checkPermission(permission)) {
callback.call();
return;
}
#ifdef Q_OS_ANDROID
// TODO make this properly async
if (QtAndroidPrivate::checkPermission(permissionName(permission)).result() != QtAndroidPrivate::PermissionResult::Authorized) {
if (QtAndroidPrivate::requestPermission(permissionName(permission)).result() != QtAndroidPrivate::PermissionResult::Authorized) {
return;
}
}
callback.call();
#endif
}
#include "moc_permissionmanager.cpp"

44
src/permissionmanager.h Normal file
View File

@@ -0,0 +1,44 @@
// SPDX-FileCopyrightText: 2022 Volker Krause <vkrause@kde.org>
// SPDX-FileCopyrightText: 2024 James Graham <james.h.graham@protonmail.com>
// SPDX-License-Identifier: LGPL-2.0-or-later
#pragma once
#include <QJSValue>
#include <QObject>
#include <QQmlEngine>
/** Permission enum for use in QML. */
namespace Permission
{
Q_NAMESPACE
enum Permission {
InvalidPermission, // captures QML errors resulting in "0" enum values
ReadCalendar,
WriteCalendar,
PostNotification,
Camera,
};
Q_ENUM_NS(Permission)
}
/** Check and request platform permissions for access to controlled resources (calendar, location, etc). */
class PermissionManager : public QObject
{
Q_OBJECT
QML_ELEMENT
QML_SINGLETON
public:
static PermissionManager &instance();
static PermissionManager *create(QQmlEngine *engine, QJSEngine *)
{
engine->setObjectOwnership(&instance(), QQmlEngine::CppOwnership);
return &instance();
}
Q_INVOKABLE static bool checkPermission(Permission::Permission permission);
Q_INVOKABLE static void requestPermission(Permission::Permission permission, QJSValue callback);
private:
explicit PermissionManager(QObject *parent = nullptr);
};