From 82f4c6b7d915fe668b0893ce1ebdd9d2e8288641 Mon Sep 17 00:00:00 2001 From: xqdoo00o Date: Mon, 26 May 2025 17:54:17 +0200 Subject: [PATCH 01/43] fix: build warning --- libs/lpac-jni/src/main/jni/lpac-jni/interface-wrapper.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 index a61fc96..007e80d 100644 --- a/libs/lpac-jni/src/main/jni/lpac-jni/interface-wrapper.c +++ b/libs/lpac-jni/src/main/jni/lpac-jni/interface-wrapper.c @@ -80,7 +80,7 @@ apdu_interface_transmit(struct euicc_ctx *ctx, uint8_t **rx, uint32_t *rx_len, c LPAC_JNI_EXCEPTION_RETURN; *rx_len = (*env)->GetArrayLength(env, ret); *rx = calloc(*rx_len, sizeof(uint8_t)); - (*env)->GetByteArrayRegion(env, ret, 0, *rx_len, *rx); + (*env)->GetByteArrayRegion(env, ret, 0, *rx_len, (jbyte *) *rx); (*env)->DeleteLocalRef(env, txArr); (*env)->DeleteLocalRef(env, ret); return 0; @@ -113,7 +113,7 @@ http_interface_transmit(struct euicc_ctx *ctx, const char *url, uint32_t *rcode, jbyteArray rxArr = (jbyteArray) (*env)->GetObjectField(env, ret, field_resp_data); *rx_len = (*env)->GetArrayLength(env, rxArr); *rx = calloc(*rx_len, sizeof(uint8_t)); - (*env)->GetByteArrayRegion(env, rxArr, 0, *rx_len, *rx); + (*env)->GetByteArrayRegion(env, rxArr, 0, *rx_len, (jbyte *) *rx); (*env)->DeleteLocalRef(env, txArr); (*env)->DeleteLocalRef(env, rxArr); (*env)->DeleteLocalRef(env, headersArr); From 149a19ca1c90696ddf1b31d0f562a82f06fb2086 Mon Sep 17 00:00:00 2001 From: xqdoo00o Date: Mon, 16 Jun 2025 03:54:02 +0200 Subject: [PATCH 02/43] fix: build warning (#194) Reviewed-on: https://gitea.angry.im/PeterCxy/OpenEUICC/pulls/194 Co-authored-by: xqdoo00o Co-committed-by: xqdoo00o --- libs/lpac-jni/src/main/jni/lpac-jni/interface-wrapper.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 index a61fc96..007e80d 100644 --- a/libs/lpac-jni/src/main/jni/lpac-jni/interface-wrapper.c +++ b/libs/lpac-jni/src/main/jni/lpac-jni/interface-wrapper.c @@ -80,7 +80,7 @@ apdu_interface_transmit(struct euicc_ctx *ctx, uint8_t **rx, uint32_t *rx_len, c LPAC_JNI_EXCEPTION_RETURN; *rx_len = (*env)->GetArrayLength(env, ret); *rx = calloc(*rx_len, sizeof(uint8_t)); - (*env)->GetByteArrayRegion(env, ret, 0, *rx_len, *rx); + (*env)->GetByteArrayRegion(env, ret, 0, *rx_len, (jbyte *) *rx); (*env)->DeleteLocalRef(env, txArr); (*env)->DeleteLocalRef(env, ret); return 0; @@ -113,7 +113,7 @@ http_interface_transmit(struct euicc_ctx *ctx, const char *url, uint32_t *rcode, jbyteArray rxArr = (jbyteArray) (*env)->GetObjectField(env, ret, field_resp_data); *rx_len = (*env)->GetArrayLength(env, rxArr); *rx = calloc(*rx_len, sizeof(uint8_t)); - (*env)->GetByteArrayRegion(env, rxArr, 0, *rx_len, *rx); + (*env)->GetByteArrayRegion(env, rxArr, 0, *rx_len, (jbyte *) *rx); (*env)->DeleteLocalRef(env, txArr); (*env)->DeleteLocalRef(env, rxArr); (*env)->DeleteLocalRef(env, headersArr); From db4645b17fa7087cfdf23deedfe6a79e2e689133 Mon Sep 17 00:00:00 2001 From: septs Date: Mon, 16 Jun 2025 03:54:32 +0200 Subject: [PATCH 03/43] feat: sas accreditation number format check (#193) Reviewed-on: https://gitea.angry.im/PeterCxy/OpenEUICC/pulls/193 Co-authored-by: septs Co-committed-by: septs --- .../angry/openeuicc/ui/EuiccInfoActivity.kt | 33 ++++++++++--------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/app-common/src/main/java/im/angry/openeuicc/ui/EuiccInfoActivity.kt b/app-common/src/main/java/im/angry/openeuicc/ui/EuiccInfoActivity.kt index 1d5f37f..4a34edd 100644 --- a/app-common/src/main/java/im/angry/openeuicc/ui/EuiccInfoActivity.kt +++ b/app-common/src/main/java/im/angry/openeuicc/ui/EuiccInfoActivity.kt @@ -27,6 +27,13 @@ import kotlinx.coroutines.launch import net.typeblog.lpac_jni.impl.PKID_GSMA_LIVE_CI import net.typeblog.lpac_jni.impl.PKID_GSMA_TEST_CI +// https://euicc-manual.osmocom.org/docs/pki/eum/accredited.json +// ref: +private val RE_SAS = Regex( + """^[A-Z]{2}-[A-Z]{2}(?:-UP)?-\d{4}T?(?:-\d+)?T?$""", + setOf(RegexOption.IGNORE_CASE), +) + class EuiccInfoActivity : BaseEuiccAccessActivity(), OpenEuiccContextMarker { companion object { private val YES_NO = Pair(R.string.yes, R.string.no) @@ -109,13 +116,14 @@ class EuiccInfoActivity : BaseEuiccAccessActivity(), OpenEuiccContextMarker { vendorInfo.firmwareVersion?.let { add(Item(R.string.euicc_info_fw_ver, it)) } vendorInfo.bootloaderVersion?.let { add(Item(R.string.euicc_info_bl_ver, it)) } } - channel.lpa.euiccInfo2.let { info -> - add(Item(R.string.euicc_info_sgp22_version, info?.sgp22Version.toString())) - add(Item(R.string.euicc_info_firmware_version, info?.euiccFirmwareVersion.toString())) - add(Item(R.string.euicc_info_globalplatform_version, info?.globalPlatformVersion.toString())) - add(Item(R.string.euicc_info_pp_version, info?.ppVersion.toString())) - add(Item(R.string.euicc_info_sas_accreditation_number, info?.sasAccreditationNumber)) - add(Item(R.string.euicc_info_free_nvram, info?.freeNvram?.let(::formatFreeSpace))) + channel.lpa.euiccInfo2?.let { info -> + add(Item(R.string.euicc_info_sgp22_version, info.sgp22Version.toString())) + add(Item(R.string.euicc_info_firmware_version, info.euiccFirmwareVersion.toString())) + add(Item(R.string.euicc_info_globalplatform_version, info.globalPlatformVersion.toString())) + add(Item(R.string.euicc_info_pp_version, info.ppVersion.toString())) + info.sasAccreditationNumber.trim().takeIf(RE_SAS::matches) + ?.let { add(Item(R.string.euicc_info_sas_accreditation_number, it.uppercase())) } + add(Item(R.string.euicc_info_free_nvram, info.freeNvram.let(::formatFreeSpace))) } channel.lpa.euiccInfo2?.euiccCiPKIdListForSigning.orEmpty().let { signers -> // SGP.28 v1.0, eSIM CI Registration Criteria (Page 5 of 9, 2019-10-24) @@ -130,18 +138,13 @@ class EuiccInfoActivity : BaseEuiccAccessActivity(), OpenEuiccContextMarker { } add(Item(R.string.euicc_info_ci_type, getString(resId))) } - val atr = channel.atr?.encodeHex() ?: getString(R.string.information_unavailable) + val atr = channel.atr?.encodeHex() ?: getString(R.string.information_unavailable) add(Item(R.string.euicc_info_atr, atr, copiedToastResId = R.string.toast_atr_copied)) } + @Suppress("SameParameterValue") private fun formatByBoolean(b: Boolean, res: Pair): String = - getString( - if (b) { - res.first - } else { - res.second - } - ) + getString(if (b) res.first else res.second) inner class EuiccInfoViewHolder(root: View) : ViewHolder(root) { private val title: TextView = root.requireViewById(R.id.euicc_info_title) From 21c04ed1795b6415f8a8953c50b4d5eb63c8bf36 Mon Sep 17 00:00:00 2001 From: Peter Cai Date: Tue, 17 Jun 2025 07:57:19 -0400 Subject: [PATCH 04/43] Allow Actions to build from any branch --- .forgejo/workflows/build-debug.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.forgejo/workflows/build-debug.yml b/.forgejo/workflows/build-debug.yml index 51e802c..660dabc 100644 --- a/.forgejo/workflows/build-debug.yml +++ b/.forgejo/workflows/build-debug.yml @@ -1,7 +1,7 @@ on: push: branches: - - 'master' + - '*' jobs: build-debug: From c0dc8ac19d38dcc899553ee66446ba3cf2dc7ffe Mon Sep 17 00:00:00 2001 From: septs Date: Tue, 8 Jul 2025 15:51:36 +0800 Subject: [PATCH 05/43] feat: profile sequence number --- .../angry/openeuicc/ui/EuiccManagementFragment.kt | 13 ++++++++++++- app-common/src/main/res/layout/euicc_profile.xml | 8 ++++++++ app-common/src/main/res/layout/fragment_euicc.xml | 1 + app-common/src/main/res/values/strings.xml | 1 + 4 files changed, 22 insertions(+), 1 deletion(-) diff --git a/app-common/src/main/java/im/angry/openeuicc/ui/EuiccManagementFragment.kt b/app-common/src/main/java/im/angry/openeuicc/ui/EuiccManagementFragment.kt index 12995ff..3c94c6c 100644 --- a/app-common/src/main/java/im/angry/openeuicc/ui/EuiccManagementFragment.kt +++ b/app-common/src/main/java/im/angry/openeuicc/ui/EuiccManagementFragment.kt @@ -347,6 +347,7 @@ open class EuiccManagementFragment : Fragment(), EuiccProfilesChangedListener, private val profileClassLabel: TextView = root.requireViewById(R.id.profile_class_label) private val profileClass: TextView = root.requireViewById(R.id.profile_class) private val profileMenu: ImageButton = root.requireViewById(R.id.profile_menu) + private val profileSeqNumber: TextView = root.requireViewById(R.id.profile_sequence_number) init { iccid.setOnClickListener { @@ -366,7 +367,9 @@ open class EuiccManagementFragment : Fragment(), EuiccProfilesChangedListener, true } - profileMenu.setOnClickListener { showOptionsMenu() } + profileMenu.setOnClickListener { + showOptionsMenu() + } } private lateinit var profile: LocalProfileInfo @@ -396,6 +399,13 @@ open class EuiccManagementFragment : Fragment(), EuiccProfilesChangedListener, iccid.transformationMethod = PasswordTransformationMethod.getInstance() } + fun setProfileSequenceNumber(index: Int) { + profileSeqNumber.text = root.context.getString( + R.string.profile_sequence_number_format, + index, + ) + } + private fun showOptionsMenu() { // Prevent users from doing multiple things at once if (invalid || swipeRefresh.isRefreshing) return @@ -461,6 +471,7 @@ open class EuiccManagementFragment : Fragment(), EuiccProfilesChangedListener, when (holder) { is ProfileViewHolder -> { holder.setProfile(profiles[position]) + holder.setProfileSequenceNumber(position + 1) } is FooterViewHolder -> { holder.attach(footerViews[position - profiles.size]) diff --git a/app-common/src/main/res/layout/euicc_profile.xml b/app-common/src/main/res/layout/euicc_profile.xml index 58d55ab..74c1d7a 100644 --- a/app-common/src/main/res/layout/euicc_profile.xml +++ b/app-common/src/main/res/layout/euicc_profile.xml @@ -129,6 +129,14 @@ app:layout_constraintTop_toBottomOf="@id/profile_class" app:layout_constraintBottom_toBottomOf="parent"/> + + diff --git a/app-common/src/main/res/layout/fragment_euicc.xml b/app-common/src/main/res/layout/fragment_euicc.xml index 4ae7523..c5fde7b 100644 --- a/app-common/src/main/res/layout/fragment_euicc.xml +++ b/app-common/src/main/res/layout/fragment_euicc.xml @@ -27,6 +27,7 @@ android:layout_height="wrap_content" android:layout_marginEnd="16dp" android:layout_marginBottom="16dp" + android:contentDescription="@string/profile_download" android:src="@drawable/ic_add" app:layout_constraintRight_toRightOf="parent" app:layout_constraintBottom_toBottomOf="parent"/> diff --git a/app-common/src/main/res/values/strings.xml b/app-common/src/main/res/values/strings.xml index 05ca15a..38bb976 100644 --- a/app-common/src/main/res/values/strings.xml +++ b/app-common/src/main/res/values/strings.xml @@ -19,6 +19,7 @@ Provisioning Operational ICCID: + #%d Enable Disable From 4ac0820bbfd5d467b4646fe5ff2d5c204b06ae23 Mon Sep 17 00:00:00 2001 From: septs Date: Thu, 10 Jul 2025 02:54:25 +0200 Subject: [PATCH 06/43] fix: improve deep-link compatibility (#198) Reviewed-on: https://gitea.angry.im/PeterCxy/OpenEUICC/pulls/198 Co-authored-by: septs Co-committed-by: septs --- app-common/src/main/AndroidManifest.xml | 5 +++-- .../im/angry/openeuicc/ui/wizard/DownloadWizardActivity.kt | 4 ++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/app-common/src/main/AndroidManifest.xml b/app-common/src/main/AndroidManifest.xml index b0324dc..44c82c0 100644 --- a/app-common/src/main/AndroidManifest.xml +++ b/app-common/src/main/AndroidManifest.xml @@ -45,8 +45,9 @@ - - + + + diff --git a/app-common/src/main/java/im/angry/openeuicc/ui/wizard/DownloadWizardActivity.kt b/app-common/src/main/java/im/angry/openeuicc/ui/wizard/DownloadWizardActivity.kt index 9e312d4..6574645 100644 --- a/app-common/src/main/java/im/angry/openeuicc/ui/wizard/DownloadWizardActivity.kt +++ b/app-common/src/main/java/im/angry/openeuicc/ui/wizard/DownloadWizardActivity.kt @@ -123,8 +123,8 @@ class DownloadWizardActivity: BaseEuiccAccessActivity() { // If we get an LPA string from deep-link intents, extract from there. // Note that `onRestoreInstanceState` could override this with user input, // but that _is_ the desired behavior. - val uri = intent.data - if (uri?.scheme == "lpa") { + val uri = intent.data ?: return + if (uri.scheme.contentEquals("lpa", ignoreCase = true)) { val parsed = LPAString.parse(uri.schemeSpecificPart) state.smdp = parsed.address state.matchingId = parsed.matchingId From 6d43a9207cbfb1c03ccf304a2c3cd117c578df34 Mon Sep 17 00:00:00 2001 From: septs Date: Wed, 16 Jul 2025 14:24:05 +0200 Subject: [PATCH 07/43] chore: simplify pretty print json string (#201) https://developer.android.com/reference/org/json/JSONObject Reviewed-on: https://gitea.angry.im/PeterCxy/OpenEUICC/pulls/201 Co-authored-by: septs Co-committed-by: septs --- .../DownloadWizardDiagnosticsFragment.kt | 4 +- .../im/angry/openeuicc/util/StringUtils.kt | 70 ------------------- 2 files changed, 3 insertions(+), 71 deletions(-) diff --git a/app-common/src/main/java/im/angry/openeuicc/ui/wizard/DownloadWizardDiagnosticsFragment.kt b/app-common/src/main/java/im/angry/openeuicc/ui/wizard/DownloadWizardDiagnosticsFragment.kt index e282196..3841868 100644 --- a/app-common/src/main/java/im/angry/openeuicc/ui/wizard/DownloadWizardDiagnosticsFragment.kt +++ b/app-common/src/main/java/im/angry/openeuicc/ui/wizard/DownloadWizardDiagnosticsFragment.kt @@ -8,6 +8,7 @@ import android.view.ViewGroup import android.widget.TextView import im.angry.openeuicc.common.R import im.angry.openeuicc.util.* +import org.json.JSONObject import java.util.Date class DownloadWizardDiagnosticsFragment : DownloadWizardActivity.DownloadWizardStepFragment() { @@ -86,9 +87,10 @@ class DownloadWizardDiagnosticsFragment : DownloadWizardActivity.DownloadWizardS ret.appendLine() val str = resp.data.decodeToString(throwOnInvalidSequence = false) + ret.appendLine( if (str.startsWith('{')) { - str.prettyPrintJson() + JSONObject(str).toString(2) } else { str } diff --git a/app-common/src/main/java/im/angry/openeuicc/util/StringUtils.kt b/app-common/src/main/java/im/angry/openeuicc/util/StringUtils.kt index 079853e..57d150b 100644 --- a/app-common/src/main/java/im/angry/openeuicc/util/StringUtils.kt +++ b/app-common/src/main/java/im/angry/openeuicc/util/StringUtils.kt @@ -41,73 +41,3 @@ fun parseIsdrAidList(s: String): List = .filter(String::isNotEmpty) .mapNotNull { runCatching(it::decodeHex).getOrNull() } .ifEmpty { listOf(EUICC_DEFAULT_ISDR_AID.decodeHex()) } - -fun String.prettyPrintJson(): String { - val ret = StringBuilder() - var inQuotes = false - var escaped = false - val indentSymbolStack = ArrayDeque() - - val addNewLine = { - ret.append('\n') - repeat(indentSymbolStack.size) { - ret.append('\t') - } - } - - var lastChar = ' ' - - for (c in this) { - when { - !inQuotes && (c == '{' || c == '[') -> { - ret.append(c) - indentSymbolStack.addLast(c) - addNewLine() - } - - !inQuotes && (c == '}' || c == ']') -> { - indentSymbolStack.removeLast() - if (lastChar != ',') { - addNewLine() - } - ret.append(c) - } - - !inQuotes && c == ',' -> { - ret.append(c) - addNewLine() - } - - !inQuotes && c == ':' -> { - ret.append(c) - ret.append(' ') - } - - inQuotes && c == '\\' -> { - ret.append(c) - escaped = true - continue - } - - !escaped && c == '"' -> { - ret.append(c) - inQuotes = !inQuotes - } - - !inQuotes && c == ' ' -> { - // Do nothing -- we ignore spaces outside of quotes by default - // This is to ensure predictable formatting - } - - else -> ret.append(c) - } - - if (escaped) { - escaped = false - } - - lastChar = c - } - - return ret.toString() -} \ No newline at end of file From 677b69cedfcae37557ca5626bb1741987ffb0a10 Mon Sep 17 00:00:00 2001 From: Peter Cai Date: Sun, 20 Jul 2025 10:29:27 -0400 Subject: [PATCH 08/43] feat: quick compatibility check Co-authored-by: septs --- app-unpriv/src/main/AndroidManifest.xml | 5 + .../openeuicc/di/UnprivilegedAppContainer.kt | 5 + .../di/UnprivilegedUiComponentFactory.kt | 7 +- .../ui/QuickCompatibilityActivity.kt | 25 ++++ .../ui/QuickCompatibilityFragment.kt | 140 ++++++++++++++++++ .../openeuicc/ui/UnprivilegedMainActivity.kt | 13 +- .../openeuicc/util/CompatibilityCheck.kt | 6 +- .../util/UnprivilegedPreferenceRepository.kt | 14 ++ .../angry/openeuicc/util/UnprivilegedUtils.kt | 6 + .../layout/activity_quick_compatibility.xml | 16 ++ .../layout/fragment_quick_compatibility.xml | 53 +++++++ app-unpriv/src/main/res/values/strings.xml | 10 ++ 12 files changed, 294 insertions(+), 6 deletions(-) create mode 100644 app-unpriv/src/main/java/im/angry/openeuicc/ui/QuickCompatibilityActivity.kt create mode 100644 app-unpriv/src/main/java/im/angry/openeuicc/ui/QuickCompatibilityFragment.kt create mode 100644 app-unpriv/src/main/java/im/angry/openeuicc/util/UnprivilegedPreferenceRepository.kt create mode 100644 app-unpriv/src/main/java/im/angry/openeuicc/util/UnprivilegedUtils.kt create mode 100644 app-unpriv/src/main/res/layout/activity_quick_compatibility.xml create mode 100644 app-unpriv/src/main/res/layout/fragment_quick_compatibility.xml diff --git a/app-unpriv/src/main/AndroidManifest.xml b/app-unpriv/src/main/AndroidManifest.xml index ce985cd..ba37079 100644 --- a/app-unpriv/src/main/AndroidManifest.xml +++ b/app-unpriv/src/main/AndroidManifest.xml @@ -23,6 +23,11 @@ + + = emptyList() + ) + } + + private val conclusion: TextView by lazy { + requireView().requireViewById(R.id.quick_availability_conclusion) + } + + private val resultSlots: TextView by lazy { + requireView().requireViewById(R.id.quick_availability_result_slots) + } + + private val resultNotes: TextView by lazy { + requireView().requireViewById(R.id.quick_availability_result_notes) + } + + private val hidden: CheckBox by lazy { + requireView().requireViewById(R.id.quick_availability_hidden) + } + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View = inflater.inflate(R.layout.fragment_quick_compatibility, container, false).apply { + requireViewById(R.id.quick_availability_device_information) + .text = formatDeviceInformation() + requireViewById