diff --git a/.gitmodules b/.gitmodules
new file mode 100644
index 0000000..f888959
--- /dev/null
+++ b/.gitmodules
@@ -0,0 +1,3 @@
+[submodule "libs/lpac-jni/src/main/jni/lpac"]
+ path = libs/lpac-jni/src/main/jni/lpac
+ url = https://github.com/estkme/lpac
diff --git a/.idea/compiler.xml b/.idea/compiler.xml
index f87d4c7..69fd9ef 100644
--- a/.idea/compiler.xml
+++ b/.idea/compiler.xml
@@ -4,9 +4,10 @@
-
-
-
+
+
+
+
\ No newline at end of file
diff --git a/.idea/gradle.xml b/.idea/gradle.xml
index e8e911f..45786ac 100644
--- a/.idea/gradle.xml
+++ b/.idea/gradle.xml
@@ -16,6 +16,7 @@
+
diff --git a/.idea/kotlinc.xml b/.idea/kotlinc.xml
index 7e340a7..e805548 100644
--- a/.idea/kotlinc.xml
+++ b/.idea/kotlinc.xml
@@ -1,6 +1,6 @@
-
+
\ No newline at end of file
diff --git a/app/build.gradle b/app/build.gradle
index 2e98b83..bf58142 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -37,7 +37,7 @@ def keystoreProperties = new Properties()
keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
android {
- compileSdk 31
+ compileSdk 34
defaultConfig {
applicationId "im.angry.openeuicc"
@@ -81,11 +81,12 @@ dependencies {
compileOnly project(':libs:hidden-apis-stub')
implementation project(':libs:hidden-apis-shim')
implementation project(":libs:lpad-sm-dp-plus-connector")
- implementation 'androidx.core:core-ktx:1.7.0'
- implementation 'androidx.appcompat:appcompat:1.4.1'
- implementation 'com.google.android.material:material:1.6.0'
- implementation 'androidx.constraintlayout:constraintlayout:2.1.3'
- implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.4.1'
+ implementation project(":libs:lpac-jni")
+ implementation 'androidx.core:core-ktx:1.12.0'
+ implementation 'androidx.appcompat:appcompat:1.6.1'
+ implementation 'com.google.android.material:material:1.10.0'
+ implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
+ implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.6.2'
implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.1.0"
implementation "androidx.cardview:cardview:1.0.0"
implementation 'com.journeyapps:zxing-android-embedded:4.3.0'
diff --git a/build.gradle b/build.gradle
index aeac5db..b7f2df2 100644
--- a/build.gradle
+++ b/build.gradle
@@ -2,8 +2,8 @@
plugins {
id 'com.android.application' version '8.1.2' apply false
id 'com.android.library' version '8.1.2' apply false
- id 'org.jetbrains.kotlin.android' version '1.6.21' apply false
- id 'org.jetbrains.kotlin.multiplatform' version '1.6.21' apply false
+ id 'org.jetbrains.kotlin.android' version '1.9.20' apply false
+ id 'org.jetbrains.kotlin.multiplatform' version '1.9.20' apply false
}
task clean(type: Delete) {
diff --git a/libs/lpac-jni/build.gradle b/libs/lpac-jni/build.gradle
new file mode 100644
index 0000000..f3c9d40
--- /dev/null
+++ b/libs/lpac-jni/build.gradle
@@ -0,0 +1,46 @@
+plugins {
+ id 'com.android.library'
+ id 'org.jetbrains.kotlin.android'
+}
+
+android {
+ namespace 'net.typeblog.lpac_jni'
+ compileSdk 33
+
+ defaultConfig {
+ minSdk 27
+
+ testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+ consumerProguardFiles "consumer-rules.pro"
+ }
+
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+ }
+ }
+ externalNativeBuild {
+ ndkBuild {
+ path "src/main/jni/Android.mk"
+ }
+ }
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
+ }
+ kotlinOptions {
+ jvmTarget = '1.8'
+ }
+}
+
+dependencies {
+
+ implementation 'androidx.core:core-ktx:1.12.0'
+ implementation platform('org.jetbrains.kotlin:kotlin-bom:1.8.0')
+ implementation 'androidx.appcompat:appcompat:1.6.1'
+ implementation 'com.google.android.material:material:1.10.0'
+ testImplementation 'junit:junit:4.13.2'
+ androidTestImplementation 'androidx.test.ext:junit:1.1.5'
+ androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
+}
\ No newline at end of file
diff --git a/libs/lpac-jni/consumer-rules.pro b/libs/lpac-jni/consumer-rules.pro
new file mode 100644
index 0000000..e69de29
diff --git a/libs/lpac-jni/proguard-rules.pro b/libs/lpac-jni/proguard-rules.pro
new file mode 100644
index 0000000..481bb43
--- /dev/null
+++ b/libs/lpac-jni/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
\ No newline at end of file
diff --git a/libs/lpac-jni/src/androidTest/java/net/typeblog/lpac_jni/ExampleInstrumentedTest.kt b/libs/lpac-jni/src/androidTest/java/net/typeblog/lpac_jni/ExampleInstrumentedTest.kt
new file mode 100644
index 0000000..581a79b
--- /dev/null
+++ b/libs/lpac-jni/src/androidTest/java/net/typeblog/lpac_jni/ExampleInstrumentedTest.kt
@@ -0,0 +1,24 @@
+package net.typeblog.lpac_jni
+
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.ext.junit.runners.AndroidJUnit4
+
+import org.junit.Test
+import org.junit.runner.RunWith
+
+import org.junit.Assert.*
+
+/**
+ * Instrumented test, which will execute on an Android device.
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+@RunWith(AndroidJUnit4::class)
+class ExampleInstrumentedTest {
+ @Test
+ fun useAppContext() {
+ // Context of the app under test.
+ val appContext = InstrumentationRegistry.getInstrumentation().targetContext
+ assertEquals("net.typeblog.lpac_jni.test", appContext.packageName)
+ }
+}
\ No newline at end of file
diff --git a/libs/lpac-jni/src/main/AndroidManifest.xml b/libs/lpac-jni/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..a5918e6
--- /dev/null
+++ b/libs/lpac-jni/src/main/AndroidManifest.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/libs/lpac-jni/src/main/java/net/typeblog/lpac_jni/ApduInterface.kt b/libs/lpac-jni/src/main/java/net/typeblog/lpac_jni/ApduInterface.kt
new file mode 100644
index 0000000..aa977a6
--- /dev/null
+++ b/libs/lpac-jni/src/main/java/net/typeblog/lpac_jni/ApduInterface.kt
@@ -0,0 +1,12 @@
+package net.typeblog.lpac_jni
+
+/*
+ * Should reflect euicc_apdu_interface in lpac/euicc/interface.h
+ */
+sealed interface ApduInterface {
+ fun connect()
+ fun disconnect()
+ fun logicalChannelOpen(aid: ByteArray): Int
+ fun logicalChannelClose(handle: Int)
+ fun transmit(tx: ByteArray): ByteArray
+}
\ No newline at end of file
diff --git a/libs/lpac-jni/src/main/java/net/typeblog/lpac_jni/HttpInterface.kt b/libs/lpac-jni/src/main/java/net/typeblog/lpac_jni/HttpInterface.kt
new file mode 100644
index 0000000..705b21e
--- /dev/null
+++ b/libs/lpac-jni/src/main/java/net/typeblog/lpac_jni/HttpInterface.kt
@@ -0,0 +1,28 @@
+package net.typeblog.lpac_jni
+
+/*
+ * Should reflect euicc_http_interface in lpac/euicc/interface.h
+ */
+sealed interface HttpInterface {
+ data class HttpResponse(val rcode: Int, val data: ByteArray) {
+ override fun equals(other: Any?): Boolean {
+ if (this === other) return true
+ if (javaClass != other?.javaClass) return false
+
+ other as HttpResponse
+
+ if (rcode != other.rcode) return false
+ if (!data.contentEquals(other.data)) return false
+
+ return true
+ }
+
+ override fun hashCode(): Int {
+ var result = rcode
+ result = 31 * result + data.contentHashCode()
+ return result
+ }
+ }
+
+ fun transmit(url: String, tx: ByteArray): HttpResponse
+}
\ No newline at end of file
diff --git a/libs/lpac-jni/src/main/java/net/typeblog/lpac_jni/LpacJni.kt b/libs/lpac-jni/src/main/java/net/typeblog/lpac_jni/LpacJni.kt
new file mode 100644
index 0000000..5dfeebe
--- /dev/null
+++ b/libs/lpac-jni/src/main/java/net/typeblog/lpac_jni/LpacJni.kt
@@ -0,0 +1,11 @@
+package net.typeblog.lpac_jni
+
+private class LpacJni {
+ init {
+ System.loadLibrary("lpac-jni")
+ }
+
+ external fun createContext(apduInterface: ApduInterface, httpInterface: HttpInterface): Long
+ external fun destroyContext(handle: Long)
+ external fun setCurrentContext(handle: Long)
+}
\ No newline at end of file
diff --git a/libs/lpac-jni/src/main/jni/Android.mk b/libs/lpac-jni/src/main/jni/Android.mk
new file mode 100644
index 0000000..cd5678c
--- /dev/null
+++ b/libs/lpac-jni/src/main/jni/Android.mk
@@ -0,0 +1,46 @@
+LOCAL_PATH := $(call my-dir)
+
+# function to find all *.c files under a directory
+define all-c-files-under
+$(patsubst ./%,%, \
+ $(shell cd $(LOCAL_PATH) ; \
+ find $(1) -name "*.c" -and -not -name ".*" -maxdepth 1) \
+ )
+endef
+
+include $(CLEAR_VARS)
+# libcjson
+LOCAL_MODULE := lpac-cjson
+LOCAL_SRC_FILES := \
+ $(call all-c-files-under, lpac/cjson)
+include $(BUILD_STATIC_LIBRARY)
+
+include $(CLEAR_VARS)
+# libasn1c, the ASN parser component from lpac
+LOCAL_MODULE := lpac-asn1c
+LOCAL_C_INCLUDES := \
+ $(LOCAL_PATH)/lpac/euicc/asn1c
+LOCAL_SRC_FILES := \
+ $(call all-c-files-under, lpac/euicc/asn1c/asn1)
+LOCAL_CFLAGS := -DHAVE_CONFIG_H
+include $(BUILD_STATIC_LIBRARY)
+
+include $(CLEAR_VARS)
+# libeuicc component from lpac, which contains the actual implementation
+LOCAL_MODULE := lpac-euicc
+LOCAL_STATIC_LIBRARIES := lpac-asn1c lpac-cjson
+LOCAL_C_INCLUDES := \
+ $(LOCAL_PATH)/lpac
+LOCAL_SRC_FILES := \
+ $(call all-c-files-under, lpac/euicc)
+include $(BUILD_STATIC_LIBRARY)
+
+include $(CLEAR_VARS)
+LOCAL_MODULE := lpac-jni
+LOCAL_STATIC_LIBRARIES := lpac-euicc
+LOCAL_C_INCLUDES := \
+ $(LOCAL_PATH)/lpac
+LOCAL_SRC_FILES := \
+ lpac-jni/lpac-jni.c \
+ lpac-jni/interface-wrapper.c
+include $(BUILD_SHARED_LIBRARY)
\ No newline at end of file
diff --git a/libs/lpac-jni/src/main/jni/Application.mk b/libs/lpac-jni/src/main/jni/Application.mk
new file mode 100644
index 0000000..e619d92
--- /dev/null
+++ b/libs/lpac-jni/src/main/jni/Application.mk
@@ -0,0 +1 @@
+APP_ABI := all
\ No newline at end of file
diff --git a/libs/lpac-jni/src/main/jni/lpac b/libs/lpac-jni/src/main/jni/lpac
new file mode 160000
index 0000000..2eaefa6
--- /dev/null
+++ b/libs/lpac-jni/src/main/jni/lpac
@@ -0,0 +1 @@
+Subproject commit 2eaefa6f8d79f68eff6a8c03932b861425767330
diff --git a/libs/lpac-jni/src/main/jni/lpac-jni/interface-wrapper.c b/libs/lpac-jni/src/main/jni/lpac-jni/interface-wrapper.c
new file mode 100644
index 0000000..a744946
--- /dev/null
+++ b/libs/lpac-jni/src/main/jni/lpac-jni/interface-wrapper.c
@@ -0,0 +1,117 @@
+#include
+#include
+#include "interface-wrapper.h"
+#include "lpac-jni.h"
+
+jmethodID method_apdu_connect;
+jmethodID method_apdu_disconnect;
+jmethodID method_apdu_logical_channel_open;
+jmethodID method_apdu_logical_channel_close;
+jmethodID method_apdu_transmit;
+
+jmethodID method_http_transmit;
+
+jfieldID field_resp_rcode;
+jfieldID field_resp_data;
+
+void interface_wrapper_init() {
+ LPAC_JNI_SETUP_ENV;
+ jclass apdu_class = (*env)->FindClass(env, "net/typeblog/lpac_jni/ApduInterface");
+ method_apdu_connect = (*env)->GetMethodID(env, apdu_class, "connect", "()V");
+ method_apdu_disconnect = (*env)->GetMethodID(env, apdu_class, "disconnect", "()V");
+ method_apdu_logical_channel_open = (*env)->GetMethodID(env, apdu_class, "logicalChannelOpen", "([B)I");
+ method_apdu_logical_channel_close = (*env)->GetMethodID(env, apdu_class, "logicalChannelClose", "(I)V");
+ method_apdu_transmit = (*env)->GetMethodID(env, apdu_class, "transmit", "([B)[B");
+
+ jclass http_class = (*env)->FindClass(env, "net/typeblog/lpac_jni/HttpInterface");
+ method_http_transmit = (*env)->GetMethodID(env, http_class, "transmit",
+ "(Ljava/lang/String;[B)Lnet/typeblog/lpac_jni/HttpInterface$HttpResponse;");
+
+ jclass resp_class = (*env)->FindClass(env, "net/typeblog/lpac_jni/HttpInterface$HttpResponse");
+ field_resp_rcode = (*env)->GetFieldID(env, resp_class, "rcode", "I");
+ field_resp_data = (*env)->GetFieldID(env, resp_class, "data", "[B");
+}
+
+static int apdu_interface_connect(void) {
+ LPAC_JNI_BEGIN;
+ LPAC_JNI_ASSERT_CTX;
+ LPAC_JNI_SETUP_ENV;
+ (*env)->CallVoidMethod(env, jni_ctx->apdu_interface, method_apdu_connect);
+ LPAC_JNI_END(!((*env)->ExceptionCheck(env) == JNI_FALSE));
+}
+
+static void apdu_interface_disconnect(void) {
+ LPAC_JNI_BEGIN;
+ LPAC_JNI_ASSERT_CTX;
+ LPAC_JNI_SETUP_ENV;
+ (*env)->CallVoidMethod(env, jni_ctx->apdu_interface, method_apdu_disconnect);
+ LPAC_JNI_END0;
+}
+
+static int apdu_interface_logical_channel_open(const uint8_t *aid, uint8_t aid_len) {
+ LPAC_JNI_BEGIN;
+ LPAC_JNI_ASSERT_CTX;
+ LPAC_JNI_SETUP_ENV;
+ jbyteArray jbarr = (*env)->NewByteArray(env, aid_len);
+ (*env)->SetByteArrayRegion(env, jbarr, 0, aid_len, (const jbyte *) aid);
+ jint ret = (*env)->CallIntMethod(env, jni_ctx->apdu_interface, method_apdu_logical_channel_open, jbarr);
+ if ((*env)->ExceptionCheck(env) == JNI_TRUE) {
+ LPAC_JNI_END(-1);
+ } else {
+ LPAC_JNI_END(ret);
+ }
+}
+
+static void apdu_interface_logical_channel_close(uint8_t channel) {
+ LPAC_JNI_BEGIN;
+ LPAC_JNI_ASSERT_CTX;
+ LPAC_JNI_SETUP_ENV;
+ (*env)->CallVoidMethod(env, jni_ctx->apdu_interface, method_apdu_logical_channel_close, channel);
+ LPAC_JNI_END0;
+}
+
+static int apdu_interface_transmit(uint8_t **rx, uint32_t *rx_len, const uint8_t *tx, uint32_t tx_len) {
+ LPAC_JNI_BEGIN;
+ LPAC_JNI_ASSERT_CTX;
+ LPAC_JNI_SETUP_ENV;
+ jbyteArray txArr = (*env)->NewByteArray(env, tx_len);
+ (*env)->SetByteArrayRegion(env, txArr, 0, tx_len, (const jbyte *) tx);
+ jbyteArray ret = (jbyteArray) (*env)->CallObjectMethod(env, jni_ctx->apdu_interface, method_apdu_transmit, txArr);
+ if ((*env)->ExceptionCheck(env) == JNI_TRUE) {
+ LPAC_JNI_END(-1);
+ }
+ *rx_len = (*env)->GetArrayLength(env, ret);
+ *rx = malloc(*rx_len * sizeof(uint8_t));
+ (*env)->GetByteArrayRegion(env, ret, 0, *rx_len, *rx);
+ LPAC_JNI_END(0);
+}
+
+static int http_interface_transmit(const char *url, uint32_t *rcode, uint8_t **rx, uint32_t *rx_len, const uint8_t *tx, uint32_t tx_len) {
+ LPAC_JNI_BEGIN;
+ LPAC_JNI_ASSERT_CTX;
+ LPAC_JNI_SETUP_ENV;
+ jstring jurl = (*env)->NewString(env, url, strlen(url));
+ jbyteArray txArr = (*env)->NewByteArray(env, tx_len);
+ (*env)->SetByteArrayRegion(env, txArr, 0, tx_len, (const jbyte *) tx);
+ jobject ret = (*env)->CallObjectMethod(env, jni_ctx->http_interface, method_http_transmit, jurl, txArr);
+ if ((*env)->ExceptionCheck(env) == JNI_TRUE) {
+ LPAC_JNI_END(-1);
+ }
+ *rcode = (*env)->GetIntField(env, ret, field_resp_rcode);
+ jbyteArray rxArr = (jbyteArray) (*env)->GetObjectField(env, ret, field_resp_data);
+ *rx_len = (*env)->GetArrayLength(env, rxArr);
+ *rx = malloc(*rx_len * sizeof(uint8_t));
+ (*env)->GetByteArrayRegion(env, rxArr, 0, *rx_len, *rx);
+ LPAC_JNI_END(0);
+}
+
+struct euicc_apdu_interface apdu_interface_wrapper = {
+ .connect = apdu_interface_connect,
+ .disconnect = apdu_interface_disconnect,
+ .logic_channel_open = apdu_interface_logical_channel_open,
+ .logic_channel_close = apdu_interface_logical_channel_close,
+ .transmit = apdu_interface_transmit
+};
+struct euicc_http_interface http_interface_wrapper = {
+ .transmit = http_interface_transmit
+};
\ No newline at end of file
diff --git a/libs/lpac-jni/src/main/jni/lpac-jni/interface-wrapper.h b/libs/lpac-jni/src/main/jni/lpac-jni/interface-wrapper.h
new file mode 100644
index 0000000..e034965
--- /dev/null
+++ b/libs/lpac-jni/src/main/jni/lpac-jni/interface-wrapper.h
@@ -0,0 +1,14 @@
+#pragma once
+#undef NDEBUG
+#include
+#include
+
+extern struct euicc_apdu_interface apdu_interface_wrapper;
+extern struct euicc_http_interface http_interface_wrapper;
+
+void interface_wrapper_init();
+
+#define LPAC_JNI_ASSERT_CTX assert(jni_ctx != NULL)
+#define LPAC_JNI_SETUP_ENV \
+ JNIEnv *env; \
+ (*jvm)->AttachCurrentThread(jvm, &env, NULL)
\ No newline at end of file
diff --git a/libs/lpac-jni/src/main/jni/lpac-jni/lpac-jni.c b/libs/lpac-jni/src/main/jni/lpac-jni/lpac-jni.c
new file mode 100644
index 0000000..4f48b2c
--- /dev/null
+++ b/libs/lpac-jni/src/main/jni/lpac-jni/lpac-jni.c
@@ -0,0 +1,49 @@
+#include
+#include
+#include
+#include "lpac-jni.h"
+#include "interface-wrapper.h"
+
+pthread_mutex_t global_lock = PTHREAD_MUTEX_INITIALIZER;
+struct lpac_jni_ctx *jni_ctx = NULL;
+JavaVM *jvm = NULL;
+
+jint JNI_OnLoad(JavaVM *vm, void *reserved) {
+ jvm = vm;
+ interface_wrapper_init();
+ return 1;
+}
+
+JNIEXPORT jlong JNICALL
+Java_net_typeblog_lpac_1jni_LpacJni_createContext(JNIEnv *env, jobject thiz,
+ jobject apdu_interface,
+ jobject http_interface) {
+ LPAC_JNI_BEGIN;
+ struct lpac_jni_ctx *_ctx = malloc(sizeof(struct lpac_jni_ctx));
+ memset(_ctx, 0, sizeof(struct lpac_jni_ctx));
+ _ctx->ctx.interface.apdu = &apdu_interface_wrapper;
+ _ctx->ctx.interface.http = &http_interface_wrapper;
+ _ctx->apdu_interface = (*env)->NewGlobalRef(env, apdu_interface);
+ _ctx->http_interface = (*env)->NewGlobalRef(env, http_interface);
+ LPAC_JNI_END((jlong) _ctx);
+}
+
+JNIEXPORT void JNICALL
+Java_net_typeblog_lpac_1jni_LpacJni_destroyContext(JNIEnv *env, jobject thiz, jlong handle) {
+ LPAC_JNI_BEGIN;
+ struct lpac_jni_ctx *_ctx = (struct lpac_jni_ctx *) handle;
+ (*env)->DeleteGlobalRef(env, _ctx->apdu_interface);
+ (*env)->DeleteGlobalRef(env, _ctx->http_interface);
+ if (jni_ctx == _ctx) {
+ jni_ctx = NULL;
+ }
+ free(_ctx);
+ LPAC_JNI_END0;
+}
+
+JNIEXPORT void JNICALL
+Java_net_typeblog_lpac_1jni_LpacJni_setCurrentContext(JNIEnv *env, jobject thiz, jlong handle) {
+ LPAC_JNI_BEGIN;
+ jni_ctx = (struct lpac_jni_ctx *) handle;
+ LPAC_JNI_END0;
+}
\ No newline at end of file
diff --git a/libs/lpac-jni/src/main/jni/lpac-jni/lpac-jni.h b/libs/lpac-jni/src/main/jni/lpac-jni/lpac-jni.h
new file mode 100644
index 0000000..7f652ce
--- /dev/null
+++ b/libs/lpac-jni/src/main/jni/lpac-jni/lpac-jni.h
@@ -0,0 +1,18 @@
+#pragma once
+#include
+#include
+#include
+
+struct lpac_jni_ctx {
+ struct euicc_ctx ctx;
+ jobject apdu_interface;
+ jobject http_interface;
+};
+
+extern JavaVM *jvm;
+extern pthread_mutex_t global_lock;
+extern struct lpac_jni_ctx *jni_ctx;
+
+#define LPAC_JNI_BEGIN pthread_mutex_lock(&global_lock)
+#define LPAC_JNI_END0 pthread_mutex_unlock(&global_lock)
+#define LPAC_JNI_END(ret) LPAC_JNI_END0; return ret
diff --git a/libs/lpac-jni/src/test/java/net/typeblog/lpac_jni/ExampleUnitTest.kt b/libs/lpac-jni/src/test/java/net/typeblog/lpac_jni/ExampleUnitTest.kt
new file mode 100644
index 0000000..0334685
--- /dev/null
+++ b/libs/lpac-jni/src/test/java/net/typeblog/lpac_jni/ExampleUnitTest.kt
@@ -0,0 +1,17 @@
+package net.typeblog.lpac_jni
+
+import org.junit.Test
+
+import org.junit.Assert.*
+
+/**
+ * Example local unit test, which will execute on the development machine (host).
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+class ExampleUnitTest {
+ @Test
+ fun addition_isCorrect() {
+ assertEquals(4, 2 + 2)
+ }
+}
\ No newline at end of file
diff --git a/libs/lpad-sm-dp-plus-connector/build.gradle b/libs/lpad-sm-dp-plus-connector/build.gradle
index 3314139..32a4686 100644
--- a/libs/lpad-sm-dp-plus-connector/build.gradle
+++ b/libs/lpad-sm-dp-plus-connector/build.gradle
@@ -26,9 +26,4 @@ task genAsn1(type: JavaExec) {
compileJava.dependsOn genAsn1
compileKotlin.dependsOn genAsn1
-description = 'LPAd SM-DP+ Connector'
-
-java {
- sourceCompatibility = JavaVersion.VERSION_1_8
- targetCompatibility = JavaVersion.VERSION_1_8
-}
\ No newline at end of file
+description = 'LPAd SM-DP+ Connector'
\ No newline at end of file
diff --git a/settings.gradle b/settings.gradle
index 534a3ba..00b4a24 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -16,3 +16,4 @@ rootProject.name = "OpenEUICC"
include ':app', ':libs:lpad-sm-dp-plus-connector'
include ':libs:hidden-apis-stub'
include ':libs:hidden-apis-shim'
+include ':libs:lpac-jni'