Compare commits

...
Sign in to create a new pull request.

7 commits

Author SHA1 Message Date
6d43a9207c chore: simplify pretty print json string (#201)
https://developer.android.com/reference/org/json/JSONObject
Reviewed-on: PeterCxy/OpenEUICC#201
Co-authored-by: septs <github@septs.pw>
Co-committed-by: septs <github@septs.pw>
2025-07-16 14:24:05 +02:00
f056e2d313 Merge pull request 'feat: profile sequence number' (#197) from septs/OpenEUICC:profile-sequence-number into master
Reviewed-on: PeterCxy/OpenEUICC#197
2025-07-10 02:54:40 +02:00
4ac0820bbf fix: improve deep-link compatibility (#198)
Reviewed-on: PeterCxy/OpenEUICC#198
Co-authored-by: septs <github@septs.pw>
Co-committed-by: septs <github@septs.pw>
2025-07-10 02:54:25 +02:00
c0dc8ac19d
feat: profile sequence number 2025-07-08 15:51:36 +08:00
21c04ed179 Allow Actions to build from any branch 2025-06-17 07:57:19 -04:00
db4645b17f feat: sas accreditation number format check (#193)
Reviewed-on: PeterCxy/OpenEUICC#193
Co-authored-by: septs <github@septs.pw>
Co-committed-by: septs <github@septs.pw>
2025-06-16 03:54:32 +02:00
149a19ca1c fix: build warning (#194)
Reviewed-on: PeterCxy/OpenEUICC#194
Co-authored-by: xqdoo00o <xqdoo00o@gmail.com>
Co-committed-by: xqdoo00o <xqdoo00o@gmail.com>
2025-06-16 03:54:02 +02:00
11 changed files with 51 additions and 94 deletions

View file

@ -1,7 +1,7 @@
on:
push:
branches:
- 'master'
- '*'
jobs:
build-debug:

View file

@ -45,8 +45,9 @@
<!-- Accepts URIs that begin with "lpa:" -->
<!-- for example: "LPA:1$..." -->
<!-- refs: https://www.iana.org/assignments/uri-schemes/prov/lpa -->
<data android:scheme="lpa"/>
<data android:sspPrefix="1$"/>
<data android:scheme="lpa" />
<data android:scheme="LPA" tools:ignore="AppLinkUrlError" />
<data android:sspPrefix="1$" />
</intent-filter>
</activity>

View file

@ -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: <https://regex101.com/r/5FFz8u>
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<Int, Int>): 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)

View file

@ -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])

View file

@ -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

View file

@ -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
}

View file

@ -41,73 +41,3 @@ fun parseIsdrAidList(s: String): List<ByteArray> =
.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<Char>()
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()
}

View file

@ -129,6 +129,14 @@
app:layout_constraintTop_toBottomOf="@id/profile_class"
app:layout_constraintBottom_toBottomOf="parent"/>
<TextView
android:id="@+id/profile_sequence_number"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginHorizontal="6dp"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="@id/iccid" />
</androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.card.MaterialCardView>

View file

@ -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"/>

View file

@ -19,6 +19,7 @@
<string name="profile_class_provisioning">Provisioning</string>
<string name="profile_class_operational">Operational</string>
<string name="iccid" translatable="false">ICCID:</string>
<string name="profile_sequence_number_format" translatable="false">#%d</string>
<string name="enable">Enable</string>
<string name="disable">Disable</string>

View file

@ -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);