Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
edbe7da
feat(jni): add jni module and service adapter stub
dorotaphanSiili Aug 20, 2025
e0e8751
feat(jni): add necessary android files
dorotaphanSiili Aug 18, 2025
664b0cf
feat(jni): extend the config to use java code
dorotaphanSiili Aug 20, 2025
e95a560
feat(jni): add the jni backend java module
dorotaphanSiili Aug 20, 2025
8ca5aaf
feat(jni): add starting android service
dorotaphanSiili Aug 18, 2025
adf5903
feat(jni): add java data types converter
dorotaphanSiili Aug 21, 2025
672011c
feat(jni): service notifies jni that it is ready
dorotaphanSiili Aug 19, 2025
9871ae6
feat(jni): service exposed for jni implemntation
dorotaphanSiili Aug 19, 2025
1509d8e
feat(jni): include all data types for service
dorotaphanSiili Aug 19, 2025
f346992
feat(jni): handle native get property
dorotaphanSiili Aug 19, 2025
1c18492
feat(jni): service handles ue property changed
dorotaphanSiili Aug 19, 2025
de510cf
feat(jni): service handles on singal
dorotaphanSiili Aug 19, 2025
4453239
feat(jni): service handles native set property
dorotaphanSiili Aug 19, 2025
88d5503
feat(jni): service handles methods
dorotaphanSiili Aug 19, 2025
3c04fcd
fix: fix for enum arrays for tests
dorotaphanSiili Jul 24, 2025
dda3e65
feat(jni): add jni client
dorotaphanSiili Aug 19, 2025
62cd854
feat(jni): use java side client for binding
dorotaphanSiili Aug 19, 2025
4e050ae
feat(jni): client implement native is ready
dorotaphanSiili Aug 19, 2025
c774558
feat(jni): client handles property changed request
dorotaphanSiili Aug 19, 2025
c24d6db
feat(jni): client handles native properties change
dorotaphanSiili Aug 19, 2025
56de59d
feat(jni): client handles signals
dorotaphanSiili Aug 20, 2025
ac84034
feat(jni): client helpers for execute methods
dorotaphanSiili Aug 20, 2025
edc04b3
feat(jni): client handles methods
dorotaphanSiili Aug 20, 2025
374a04c
feat(jni): add a jni readme
dorotaphanSiili Aug 22, 2025
4d9c9f1
fix(jni): adapter handles string data types
dorotaphanSiili Sep 4, 2025
ebf6234
feat(jni): adapter handles bool types
dorotaphanSiili Sep 4, 2025
6bc1fba
fix(jni): client handle strings data
dorotaphanSiili Sep 4, 2025
c49696b
feat(jni): client handle boolean data
dorotaphanSiili Sep 4, 2025
1abe4f0
fix(jni): converter handles string types
dorotaphanSiili Sep 4, 2025
0254810
feat(jni): converter handles bool types
dorotaphanSiili Sep 4, 2025
7b6adde
fix(jni): fix adapter strings data handling
dorotaphanSiili Sep 16, 2025
40f6e11
fix(jni): change logs severity
dorotaphanSiili Sep 16, 2025
2c8acef
feat(jni): add non android tests
dorotaphanSiili Sep 16, 2025
528ed3d
fix(jni): for java service starter for many modules
dorotaphanSiili Sep 25, 2025
f669097
fix(jni): fix connection status changed for many modules
dorotaphanSiili Sep 25, 2025
570612c
fix(jni): fix client void method
dorotaphanSiili Sep 25, 2025
63a411c
fix(jni): fix upl module names
dorotaphanSiili Sep 25, 2025
f6339f6
fix(jni): tests
dorotaphanSiili Sep 25, 2025
e453dda
fix(jni): handling arrays for long types
dorotaphanSiili Sep 25, 2025
764dfbe
fix(jni): client signls are now safe to call while destroying object
dorotaphanSiili Sep 26, 2025
a790e23
fix(jni): fix tests
dorotaphanSiili Sep 30, 2025
ecaedde
fix(jni): fix converting simple type arrays for jni
dorotaphanSiili Sep 30, 2025
9f27e9f
feat(jni): improve passing args to async task
dorotaphanSiili Sep 30, 2025
16a74ef
fix(jni): fix starting service for ue5.6
dorotaphanSiili Oct 2, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .conform.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,5 @@ policies:
- "docs"
- "cli"
- "deps"
- "jni"
descriptionLength: 72
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,4 @@ The template offers the following feature switches which can be enabled during c
* `monitor`: create a thin decorator class which can be used to log traffic going through API layer
* `olink`: create the adaption layer for the [OLink](https://docs.apigear.io/docs/advanced/protocols/objectlink/intro) protocol. This can be used to connect to the simulation or other technologies like python, pure c++ etc..
* `msgbus`: create the adaption layer for the [UE MsgBus](https://dev.epicgames.com/documentation/en-us/unreal-engine/API/Runtime/Messaging) protocol. This can be used for communication inside the same UE app or between different instances, including across network interfaces.
* `jni`: create the jni adaption layer for the android messenger communication provided by apigear java template. Please read more on a separate document readme_jni.
102 changes: 102 additions & 0 deletions readme_jni.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
# Unreal Jni Adapter

The feature "jni" provides the jni adaption layer for the android messenger communication, which for each interface must be generated with ApiGear java template.
The android build requires the modules provided with the java template. They are compiled within an unreal application with its build system. <br/>
Make also sure that your unreal app has proper settings selected to make it available to run on android devices (like allowing large obb files or some android permissions enabled).

## Code generation

Java template provides features:
**api, stub, android, jnibridge** - which a required for the unreal jni feature to work. <br>
You can also take a look at the **testclientapp** and **testserviceapp** features which generates a simple example of client or service android app. Those apps only serve first defined interface, and require user to fill partially code for buttons - you need to provide the arguments values. You may use those apps as a starting point to test your unreal side app.

The code generated by java template starting form the top-level folder for the module should be placed in your modules Jni plugin under android folder.
For example for the module named `"HelloWorld"` the generated folder `helloworld` the path should be:
```
YourProject/Plugins/HelloWorld/Source/HelloWorldJni/android
```
You can achieve this by configuring your `*.solution.yaml` file e.g. like that

```
schema: "apigear.solution/1.0"
name: helloworld
version: "0.1.0"

layers:
- name: unreal
inputs:
- myhelloworld.module.yaml
output: ../relative-path-to/YourProject
template: apigear-io/template-unreal //TODO MINIMAL VERSION
features: ["api","jni","stubs"]
- name: java
inputs:
- myhelloworld.module.yaml
output: ../relative-path-to/YourProject/Plugins/HelloWorld/Source/HelloWorldJni/android
template: apigear-io/template-java
features: ["api","android","stubs","jnibridge"]
```
Remember that re-generation may overwrite all your manual changes to generated files.

### Versions
Currently minimum supported sdk version for android is 33. <br>
Currently tested UE version is 5.5.

## How to use

Generated code provides:
* Jni Service Adapter
* Jni Client

Have in mind the most probably you'll need only one side in your app. The generated objects are a *game subssystems* and they are always started. Moreover the android service starts during the Service Adapter initialization. It will not collide with your client trying to connect other implementation of the service as you need to specify package from which you'd like to use the service, but still may be not wanted overhead in your app.

### Jni Service Adapter

The Jni Service Adapter treats exposes your Local Implementation of the api (requires setting a backend) as an android service. It is generated per interface described in your `*.module.yaml` <br>
When the Jni Service Adapter is initialized it creates an android service, with a jni backend implemented by that Jni Service Adapter.
Notice that there will be as many `andorid services` as the interfaces declared in the `*.module.yaml` file. When it comes to threads - no extra thread is spawned for each service, they are all in the applications thread. <br>
All the services are already exposed in the unreal app `AndroidManifest.xml`.

### Jni Client
Jni Client adapter is generated per interface. It uses the same interfaces as other ApiGear generated versions (OlinkClient, MessageBusClient or the local implementation).<br>
It is recommended to use it through that interface, which allows you to easy change of the "data input" and test your application first on your desktop e.g. using OLink simulation.<br>
To make your client work you need to bind to a **running service**. <br>
The client does not start any service while trying to bind. The re-connection is not automated neither when service died nor when it wasn't started at all.<br>
The binding may be performed from the blueprints. It requires the package name of the app that provides (and starts) the requested android service and a unique connection Id (among the this service).<br>
The client unbinds automatically on deinitialization.
All the necessary queries and permissions are configured in the unreal app` AndroidManifest.xml`.

## Application permissions

Here is a suggested configuration of the unreal project for automotive.
You can use settings in unreal editor or you can change your DefaultEngine.ini file.

/***

[/Script/AndroidRuntimeSettings.AndroidRuntimeSettings]
PackageName=your.pacakge.[PROJECT]
...
MinSDKVersion=33
TargetSDKVersion=33
...
bDisableVerifyOBBOnStartUp=False
bForceSmallOBBFiles=False
bAllowLargeOBBFiles=True
...
ExtraApplicationNodeTags=android:requestLegacyExternalStorage="true"
ExtraPermissions=android.permission.READ_MEDIA_IMAGES
ExtraPermissions=android.permission.WRITE_EXTERNAL_STORAGE
ExtraPermissions=android.permission.READ_MEDIA_VIDEO
ExtraPermissions=android.permission.READ_MEDIA_AUDIO
ExtraPermissions=android.permission.READ_EXTERNAL_STORAGE
ExtraPermissions=android.permission.MANAGE_EXTERNAL_STORAGE

***/

Remember to fill the `PackageName` field with `your.pacakge.[PROJECT]` especially when providing the service side. `your.package` is the package name that all the client should provide on binding to bind this service.

The minimum target version should not be lower than the one that is required for java template.

The OBB settings allow the unreal app to start on android device. By default android does not allow large OBB files, but unreal apps usually have them large.

Unreal app also requires some extra permissions.
56 changes: 56 additions & 0 deletions rules.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -473,3 +473,59 @@ features:
- source: "module/Source/modulemsgbus/Private/Tests/msgbusfixture.cpp.tpl"
target: "Private/Tests/{{Camel .Module.Name}}{{Camel .Interface.Name}}MsgBusFixture.cpp"
preserve: true
- name: jni
requires:
- api
- plugin
scopes:
- match: module
prefix: "Plugins/{{Camel .Module.Name}}/"
documents:
- source: "module/Source/modulejni/Private/modulejni.cpp.tpl"
target: "Source/{{Camel .Module.Name}}Jni/Private/Generated/{{Camel .Module.Name}}Jni.cpp"
- source: "module/Source/modulejni/Public/modulejni.h.tpl"
target: "Source/{{Camel .Module.Name}}Jni/Public/{{Camel .Module.Name}}/{{Camel .Module.Name}}Jni.h"
- source: "module/Source/modulejni/modulejni.Build.cs.tpl"
target: "Source/{{Camel .Module.Name}}Jni/{{Camel .Module.Name}}Jni.Build.cs"
- source: "module/Source/modulejni/jni_UPL.xml.tpl"
target: "Source/{{Camel .Module.Name}}Jni/{{Camel .Module.Name}}_JNI_UPL.xml"
- source: "module/Source/modulejni/android/buildsystem/config.gradle"
target: "android/buildsystem/config.gradle"
raw: true
- source: "module/Source/modulejni/android/buildsystem/dependencies.gradle"
target: "android/buildsystem/dependencies.gradle"
raw: true
- source: "module/Source/modulejni/Private/Generated/jni/datajavaconverter.cpp.tpl"
target: "Source/{{Camel .Module.Name}}Jni/Private/Generated/Jni/{{Camel .Module.Name}}DataJavaConverter.cpp"
- source: "module/Source/modulejni/Public/Generated/jni/datajavaconverter.h.tpl"
target: "Source/{{Camel .Module.Name}}Jni/Public/{{Camel .Module.Name}}/Generated/Jni/{{Camel .Module.Name}}DataJavaConverter.h"
- source: "module/Source/modulejni/Public/Generated/jni/jniconnectionstatus.h.tpl"
target: "Source/{{Camel .Module.Name}}Jni/Public/{{Camel .Module.Name}}//Generated/Jni/{{Camel .Module.Name}}JniConnectionStatus.h"
- match: interface
prefix: "Plugins/{{Camel .Module.Name}}/Source/{{Camel .Module.Name}}Jni/"
documents:
- source: "module/Source/modulejni/Public/Generated/jni/jniadapter.h.tpl"
target: "Public/{{Camel .Module.Name}}/Generated/Jni/{{Camel .Module.Name}}{{Camel .Interface.Name}}JniAdapter.h"
- source: "module/Source/modulejni/Private/Generated/jni/jniadapter.cpp.tpl"
target: "Private/Generated/Jni/{{Camel .Module.Name}}{{Camel .Interface.Name}}JniAdapter.cpp"
- source: "module/Source/modulejni/Public/Generated/jni/jniclient.h.tpl"
target: "Public/{{Camel .Module.Name}}/Generated/Jni/{{Camel .Module.Name}}{{Camel .Interface.Name}}JniClient.h"
- source: "module/Source/modulejni/Private/Generated/jni/jniclient.cpp.tpl"
target: "Private/Generated/Jni/{{Camel .Module.Name}}{{Camel .Interface.Name}}JniClient.cpp"
- name: jni_tests
requires:
- jni
- stubs
scopes:
- match: interface
prefix: "Plugins/{{Camel .Module.Name}}/Source/{{Camel .Module.Name}}Jni/"
documents:
- source: "module/Source/modulejni/Private/Tests/jni.spec.cpp.tpl"
target: "Private/Tests/{{Camel .Module.Name}}{{Camel .Interface.Name}}Jni.spec.cpp"
preserve: true
- source: "module/Source/modulejni/Private/Tests/jnifixture.h.tpl"
target: "Private/Tests/{{Camel .Module.Name}}{{Camel .Interface.Name}}JniFixture.h"
preserve: true
- source: "module/Source/modulejni/Private/Tests/jnifixture.cpp.tpl"
target: "Private/Tests/{{Camel .Module.Name}}{{Camel .Interface.Name}}JniFixture.cpp"
preserve: true
2 changes: 1 addition & 1 deletion templates/buildTestPlugins.bat.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ if %ERRORLEVEL% GEQ 1 exit /b %ERRORLEVEL%
{{ end }}

@REM run build and tests
call :buildTestPlugins "%ProjectTarget_path%/TP_Blank.uproject" %script_path% ".Impl.{{ if .Features.olink -}}+.OLink.{{ end }}{{ if .Features.msgbus_tests -}}+.MsgBus.{{ end }}"
call :buildTestPlugins "%ProjectTarget_path%/TP_Blank.uproject" %script_path% ".Impl.{{ if .Features.olink -}}+.OLink.{{ end }}{{ if .Features.msgbus_tests -}}+.MsgBus.{{ end }}{{ if .Features.jni_tests -}}+.Jni.{{ end }}"
exit /b 0

@REM function implementations
Expand Down
2 changes: 1 addition & 1 deletion templates/buildTestPlugins.sh.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -75,5 +75,5 @@ mkdir -p "$ProjectTarget_path/Plugins/{{Camel .Name}}" && cp -rf "$script_path/P
if [ $? -ne 0 ]; then exit 1; fi;
{{ end }}

buildTestPlugins "$ProjectTarget_path/TP_Blank.uproject" "$script_path" ".Impl.{{ if .Features.olink -}}+.OLink.{{ end }}{{ if .Features.msgbus_tests -}}+.MsgBus.{{ end }}"
buildTestPlugins "$ProjectTarget_path/TP_Blank.uproject" "$script_path" ".Impl.{{ if .Features.olink -}}+.OLink.{{ end }}{{ if .Features.msgbus_tests -}}+.MsgBus.{{ end }}{{ if .Features.jni_tests -}}+.Jni.{{ end }}"
if [ $buildresult -ne 0 ]; then exit 1; fi;
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,22 @@ TArray<{{$class}}> createTest{{$class }}Array()
return TestValueArray;
}
{{ end }}

{{- range .Module.Enums }}
{{- $class := printf "E%s%s" $ModuleName .Name }}
{{- $quickFixclass := printf "F%s%s" $ModuleName .Name }}
TArray<{{$class}}> createTest{{$quickFixclass }}Array()
{
TArray<{{$class}}> TestValueArray;
{{- $moduleEnumName := printf "%s%s" $ModuleName .Name }}
{{if len .Members }}
{{- $member:= (index .Members 0) }}
{{$class}} val = {{$class}}::{{ abbreviate $moduleEnumName }}_{{ Camel $member.Name }};
TestValueArray.Add(val);
{{- end }}
return TestValueArray;
}

{{ end }}

#endif // WITH_DEV_AUTOMATION_TESTS
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,10 @@ constexpr int {{$ModuleName}}TestFilterMask = EAutomationTestFlags::ApplicationC

TArray<{{$class}}> {{$API_MACRO}} createTest{{$class }}Array();
{{ end }}

{{- range .Module.Enums }}
{{- $class := printf "E%s%s" $ModuleName .Name }}
{{- $quickFixclass := printf "F%s%s" $ModuleName .Name }}
TArray<{{$class}}> {{$API_MACRO}} createTest{{$quickFixclass }}Array();
{{ end }}
#endif // WITH_DEV_AUTOMATION_TESTS
Loading
Loading