diff --git a/libs/lpac-jni/src/main/java/net/typeblog/lpac_jni/ProfileDownloadCallback.kt b/libs/lpac-jni/src/main/java/net/typeblog/lpac_jni/ProfileDownloadCallback.kt index 289ddf6..6193a7a 100644 --- a/libs/lpac-jni/src/main/java/net/typeblog/lpac_jni/ProfileDownloadCallback.kt +++ b/libs/lpac-jni/src/main/java/net/typeblog/lpac_jni/ProfileDownloadCallback.kt @@ -1,16 +1,19 @@ package net.typeblog.lpac_jni +import android.util.Log + interface ProfileDownloadCallback { companion object { - fun lookupStateFromProgress(progress: Int): DownloadState = - when (progress) { - 0 -> DownloadState.Preparing - 20 -> DownloadState.Connecting - 40 -> DownloadState.Authenticating - 60 -> DownloadState.Downloading - 80 -> DownloadState.Finalizing - else -> throw IllegalArgumentException("Unknown state") - } + private const val TAG = "ProfileDownloadCallback" + + fun lookupStateFromProgress(progress: Int): DownloadState = when (progress) { + 0 -> DownloadState.Preparing + 20 -> DownloadState.Connecting + 40 -> DownloadState.Authenticating + 60 -> DownloadState.Downloading + 80 -> DownloadState.Finalizing + else -> throw IllegalArgumentException("Unknown state") + } } enum class DownloadState(val progress: Int) { @@ -21,5 +24,18 @@ interface ProfileDownloadCallback { Finalizing(80), // load bpp } + data class ProfileMetadata( + val iccid: String, + val serviceProviderName: String?, + val profileName: String?, + val iconType: String?, + val icon: String?, + val profileClass: String?, + ) + fun onStateUpdate(state: DownloadState) + + fun onProfileMetadata(metadata: ProfileMetadata) { + Log.d(TAG, "Received profile metadata: $metadata") + } } \ No newline at end of file diff --git a/libs/lpac-jni/src/main/jni/lpac-jni/lpac-download.c b/libs/lpac-jni/src/main/jni/lpac-jni/lpac-download.c index bae2ee8..5bd9f51 100644 --- a/libs/lpac-jni/src/main/jni/lpac-jni/lpac-download.c +++ b/libs/lpac-jni/src/main/jni/lpac-jni/lpac-download.c @@ -1,3 +1,4 @@ +#include #include #include #include @@ -12,6 +13,9 @@ jobject download_state_downloading; jobject download_state_finalizing; jmethodID on_state_update; +jmethodID on_profile_metadata; + +#define PROFILE_METADATA_CLASS "net/typeblog/lpac_jni/ProfileDownloadCallback$ProfileMetadata" void lpac_download_init() { LPAC_JNI_SETUP_ENV; @@ -54,6 +58,59 @@ void lpac_download_init() { "net/typeblog/lpac_jni/ProfileDownloadCallback"); on_state_update = (*env)->GetMethodID(env, download_callback_class, "onStateUpdate", "(Lnet/typeblog/lpac_jni/ProfileDownloadCallback$DownloadState;)V"); + on_profile_metadata = (*env)->GetMethodID(env, download_callback_class, "onProfileMetadata", + "(L" PROFILE_METADATA_CLASS ";)V"); +} + +static jobject build_profile_metadata(JNIEnv *env, struct es8p_metadata *metadata) { + if (metadata == NULL) return NULL; + + jclass profile_metadata_class = (*env)->FindClass(env, PROFILE_METADATA_CLASS); + jmethodID profile_metadata_constructor = (*env)->GetMethodID( + env, profile_metadata_class, "", + // iccid, serviceProviderName, profileName, iconType, icon, profileClass + "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V" + ); + + const char *icon_type; + const char *profile_class; + + switch (metadata->iconType) { + case ES10C_ICON_TYPE_JPEG: + icon_type = "jpeg"; + break; + case ES10C_ICON_TYPE_PNG: + icon_type = "png"; + break; + default: + icon_type = "unknown"; + break; + } + + switch (metadata->profileClass) { + case ES10C_PROFILE_CLASS_TEST: + profile_class = "test"; + break; + case ES10C_PROFILE_CLASS_PROVISIONING: + profile_class = "provisioning"; + break; + case ES10C_PROFILE_CLASS_OPERATIONAL: + profile_class = "operational"; + break; + default: + profile_class = "unknown"; + break; + } + + return (*env)->NewObject( + env, profile_metadata_class, profile_metadata_constructor, + toJString(env, metadata->iccid), + toJString(env, metadata->serviceProviderName), + toJString(env, metadata->profileName), + toJString(env, icon_type), + toJString(env, metadata->icon), + toJString(env, profile_class) + ); } JNIEXPORT jint JNICALL @@ -63,6 +120,7 @@ Java_net_typeblog_lpac_1jni_LpacJni_downloadProfile(JNIEnv *env, jobject thiz, j jobject callback) { struct euicc_ctx *ctx = (struct euicc_ctx *) handle; struct es10b_load_bound_profile_package_result es10b_load_bound_profile_package_result; + struct es8p_metadata *profile_metadata = NULL; const char *_confirmation_code = NULL; const char *_matching_id = NULL; const char *_smdp = NULL; @@ -109,6 +167,17 @@ Java_net_typeblog_lpac_1jni_LpacJni_downloadProfile(JNIEnv *env, jobject thiz, j goto out; } + const char *b64_profileMetadata = ctx->http._internal.prepare_download_param->b64_profileMetadata; + if (b64_profileMetadata != NULL) { + ret = es8p_metadata_parse(&profile_metadata, b64_profileMetadata); + if (ret < 0) { + ret = -ES10B_ERROR_REASON_UNDEFINED; + goto out; + } + (*env)->CallVoidMethod(env, callback, on_profile_metadata, + build_profile_metadata(env, profile_metadata)); + } + (*env)->CallVoidMethod(env, callback, on_state_update, download_state_downloading); ret = es10b_prepare_download(ctx, _confirmation_code); syslog(LOG_INFO, "es10b_prepare_download %d", ret); @@ -134,6 +203,8 @@ Java_net_typeblog_lpac_1jni_LpacJni_downloadProfile(JNIEnv *env, jobject thiz, j out: // We expect Java side to call cancelSessions after any error -- thus, `euicc_http_cleanup` is done there // This is so that Java side can access the last HTTP and/or APDU errors when we return. + if (profile_metadata != NULL) + es8p_metadata_free(&profile_metadata); if (_confirmation_code != NULL) (*env)->ReleaseStringUTFChars(env, confirmation_code, _confirmation_code); if (_matching_id != NULL) @@ -181,4 +252,4 @@ Java_net_typeblog_lpac_1jni_LpacJni_downloadErrCodeToString(JNIEnv *env, jobject default: return toJString(env, "ES10B_ERROR_REASON_UNDEFINED"); } -} \ No newline at end of file +}