forked from PeterCxy/OpenEUICC
Compare commits
7 commits
Author | SHA1 | Date | |
---|---|---|---|
6d43a9207c | |||
f056e2d313 | |||
4ac0820bbf | |||
c0dc8ac19d | |||
21c04ed179 | |||
db4645b17f | |||
149a19ca1c |
11 changed files with 51 additions and 94 deletions
|
@ -1,7 +1,7 @@
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- 'master'
|
- '*'
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build-debug:
|
build-debug:
|
||||||
|
|
|
@ -45,8 +45,9 @@
|
||||||
<!-- Accepts URIs that begin with "lpa:" -->
|
<!-- Accepts URIs that begin with "lpa:" -->
|
||||||
<!-- for example: "LPA:1$..." -->
|
<!-- for example: "LPA:1$..." -->
|
||||||
<!-- refs: https://www.iana.org/assignments/uri-schemes/prov/lpa -->
|
<!-- refs: https://www.iana.org/assignments/uri-schemes/prov/lpa -->
|
||||||
<data android:scheme="lpa"/>
|
<data android:scheme="lpa" />
|
||||||
<data android:sspPrefix="1$"/>
|
<data android:scheme="LPA" tools:ignore="AppLinkUrlError" />
|
||||||
|
<data android:sspPrefix="1$" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
|
|
|
@ -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_LIVE_CI
|
||||||
import net.typeblog.lpac_jni.impl.PKID_GSMA_TEST_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 {
|
class EuiccInfoActivity : BaseEuiccAccessActivity(), OpenEuiccContextMarker {
|
||||||
companion object {
|
companion object {
|
||||||
private val YES_NO = Pair(R.string.yes, R.string.no)
|
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.firmwareVersion?.let { add(Item(R.string.euicc_info_fw_ver, it)) }
|
||||||
vendorInfo.bootloaderVersion?.let { add(Item(R.string.euicc_info_bl_ver, it)) }
|
vendorInfo.bootloaderVersion?.let { add(Item(R.string.euicc_info_bl_ver, it)) }
|
||||||
}
|
}
|
||||||
channel.lpa.euiccInfo2.let { info ->
|
channel.lpa.euiccInfo2?.let { info ->
|
||||||
add(Item(R.string.euicc_info_sgp22_version, info?.sgp22Version.toString()))
|
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_firmware_version, info.euiccFirmwareVersion.toString()))
|
||||||
add(Item(R.string.euicc_info_globalplatform_version, info?.globalPlatformVersion.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_pp_version, info.ppVersion.toString()))
|
||||||
add(Item(R.string.euicc_info_sas_accreditation_number, info?.sasAccreditationNumber))
|
info.sasAccreditationNumber.trim().takeIf(RE_SAS::matches)
|
||||||
add(Item(R.string.euicc_info_free_nvram, info?.freeNvram?.let(::formatFreeSpace)))
|
?.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 ->
|
channel.lpa.euiccInfo2?.euiccCiPKIdListForSigning.orEmpty().let { signers ->
|
||||||
// SGP.28 v1.0, eSIM CI Registration Criteria (Page 5 of 9, 2019-10-24)
|
// 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)))
|
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))
|
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 =
|
private fun formatByBoolean(b: Boolean, res: Pair<Int, Int>): String =
|
||||||
getString(
|
getString(if (b) res.first else res.second)
|
||||||
if (b) {
|
|
||||||
res.first
|
|
||||||
} else {
|
|
||||||
res.second
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
inner class EuiccInfoViewHolder(root: View) : ViewHolder(root) {
|
inner class EuiccInfoViewHolder(root: View) : ViewHolder(root) {
|
||||||
private val title: TextView = root.requireViewById(R.id.euicc_info_title)
|
private val title: TextView = root.requireViewById(R.id.euicc_info_title)
|
||||||
|
|
|
@ -347,6 +347,7 @@ open class EuiccManagementFragment : Fragment(), EuiccProfilesChangedListener,
|
||||||
private val profileClassLabel: TextView = root.requireViewById(R.id.profile_class_label)
|
private val profileClassLabel: TextView = root.requireViewById(R.id.profile_class_label)
|
||||||
private val profileClass: TextView = root.requireViewById(R.id.profile_class)
|
private val profileClass: TextView = root.requireViewById(R.id.profile_class)
|
||||||
private val profileMenu: ImageButton = root.requireViewById(R.id.profile_menu)
|
private val profileMenu: ImageButton = root.requireViewById(R.id.profile_menu)
|
||||||
|
private val profileSeqNumber: TextView = root.requireViewById(R.id.profile_sequence_number)
|
||||||
|
|
||||||
init {
|
init {
|
||||||
iccid.setOnClickListener {
|
iccid.setOnClickListener {
|
||||||
|
@ -366,7 +367,9 @@ open class EuiccManagementFragment : Fragment(), EuiccProfilesChangedListener,
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
profileMenu.setOnClickListener { showOptionsMenu() }
|
profileMenu.setOnClickListener {
|
||||||
|
showOptionsMenu()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private lateinit var profile: LocalProfileInfo
|
private lateinit var profile: LocalProfileInfo
|
||||||
|
@ -396,6 +399,13 @@ open class EuiccManagementFragment : Fragment(), EuiccProfilesChangedListener,
|
||||||
iccid.transformationMethod = PasswordTransformationMethod.getInstance()
|
iccid.transformationMethod = PasswordTransformationMethod.getInstance()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun setProfileSequenceNumber(index: Int) {
|
||||||
|
profileSeqNumber.text = root.context.getString(
|
||||||
|
R.string.profile_sequence_number_format,
|
||||||
|
index,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
private fun showOptionsMenu() {
|
private fun showOptionsMenu() {
|
||||||
// Prevent users from doing multiple things at once
|
// Prevent users from doing multiple things at once
|
||||||
if (invalid || swipeRefresh.isRefreshing) return
|
if (invalid || swipeRefresh.isRefreshing) return
|
||||||
|
@ -461,6 +471,7 @@ open class EuiccManagementFragment : Fragment(), EuiccProfilesChangedListener,
|
||||||
when (holder) {
|
when (holder) {
|
||||||
is ProfileViewHolder -> {
|
is ProfileViewHolder -> {
|
||||||
holder.setProfile(profiles[position])
|
holder.setProfile(profiles[position])
|
||||||
|
holder.setProfileSequenceNumber(position + 1)
|
||||||
}
|
}
|
||||||
is FooterViewHolder -> {
|
is FooterViewHolder -> {
|
||||||
holder.attach(footerViews[position - profiles.size])
|
holder.attach(footerViews[position - profiles.size])
|
||||||
|
|
|
@ -123,8 +123,8 @@ class DownloadWizardActivity: BaseEuiccAccessActivity() {
|
||||||
// If we get an LPA string from deep-link intents, extract from there.
|
// If we get an LPA string from deep-link intents, extract from there.
|
||||||
// Note that `onRestoreInstanceState` could override this with user input,
|
// Note that `onRestoreInstanceState` could override this with user input,
|
||||||
// but that _is_ the desired behavior.
|
// but that _is_ the desired behavior.
|
||||||
val uri = intent.data
|
val uri = intent.data ?: return
|
||||||
if (uri?.scheme == "lpa") {
|
if (uri.scheme.contentEquals("lpa", ignoreCase = true)) {
|
||||||
val parsed = LPAString.parse(uri.schemeSpecificPart)
|
val parsed = LPAString.parse(uri.schemeSpecificPart)
|
||||||
state.smdp = parsed.address
|
state.smdp = parsed.address
|
||||||
state.matchingId = parsed.matchingId
|
state.matchingId = parsed.matchingId
|
||||||
|
|
|
@ -8,6 +8,7 @@ import android.view.ViewGroup
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import im.angry.openeuicc.common.R
|
import im.angry.openeuicc.common.R
|
||||||
import im.angry.openeuicc.util.*
|
import im.angry.openeuicc.util.*
|
||||||
|
import org.json.JSONObject
|
||||||
import java.util.Date
|
import java.util.Date
|
||||||
|
|
||||||
class DownloadWizardDiagnosticsFragment : DownloadWizardActivity.DownloadWizardStepFragment() {
|
class DownloadWizardDiagnosticsFragment : DownloadWizardActivity.DownloadWizardStepFragment() {
|
||||||
|
@ -86,9 +87,10 @@ class DownloadWizardDiagnosticsFragment : DownloadWizardActivity.DownloadWizardS
|
||||||
ret.appendLine()
|
ret.appendLine()
|
||||||
|
|
||||||
val str = resp.data.decodeToString(throwOnInvalidSequence = false)
|
val str = resp.data.decodeToString(throwOnInvalidSequence = false)
|
||||||
|
|
||||||
ret.appendLine(
|
ret.appendLine(
|
||||||
if (str.startsWith('{')) {
|
if (str.startsWith('{')) {
|
||||||
str.prettyPrintJson()
|
JSONObject(str).toString(2)
|
||||||
} else {
|
} else {
|
||||||
str
|
str
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,73 +41,3 @@ fun parseIsdrAidList(s: String): List<ByteArray> =
|
||||||
.filter(String::isNotEmpty)
|
.filter(String::isNotEmpty)
|
||||||
.mapNotNull { runCatching(it::decodeHex).getOrNull() }
|
.mapNotNull { runCatching(it::decodeHex).getOrNull() }
|
||||||
.ifEmpty { listOf(EUICC_DEFAULT_ISDR_AID.decodeHex()) }
|
.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()
|
|
||||||
}
|
|
|
@ -129,6 +129,14 @@
|
||||||
app:layout_constraintTop_toBottomOf="@id/profile_class"
|
app:layout_constraintTop_toBottomOf="@id/profile_class"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"/>
|
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>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
||||||
</com.google.android.material.card.MaterialCardView>
|
</com.google.android.material.card.MaterialCardView>
|
||||||
|
|
|
@ -27,6 +27,7 @@
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginEnd="16dp"
|
android:layout_marginEnd="16dp"
|
||||||
android:layout_marginBottom="16dp"
|
android:layout_marginBottom="16dp"
|
||||||
|
android:contentDescription="@string/profile_download"
|
||||||
android:src="@drawable/ic_add"
|
android:src="@drawable/ic_add"
|
||||||
app:layout_constraintRight_toRightOf="parent"
|
app:layout_constraintRight_toRightOf="parent"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"/>
|
app:layout_constraintBottom_toBottomOf="parent"/>
|
||||||
|
|
|
@ -19,6 +19,7 @@
|
||||||
<string name="profile_class_provisioning">Provisioning</string>
|
<string name="profile_class_provisioning">Provisioning</string>
|
||||||
<string name="profile_class_operational">Operational</string>
|
<string name="profile_class_operational">Operational</string>
|
||||||
<string name="iccid" translatable="false">ICCID:</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="enable">Enable</string>
|
||||||
<string name="disable">Disable</string>
|
<string name="disable">Disable</string>
|
||||||
|
|
|
@ -80,7 +80,7 @@ apdu_interface_transmit(struct euicc_ctx *ctx, uint8_t **rx, uint32_t *rx_len, c
|
||||||
LPAC_JNI_EXCEPTION_RETURN;
|
LPAC_JNI_EXCEPTION_RETURN;
|
||||||
*rx_len = (*env)->GetArrayLength(env, ret);
|
*rx_len = (*env)->GetArrayLength(env, ret);
|
||||||
*rx = calloc(*rx_len, sizeof(uint8_t));
|
*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, txArr);
|
||||||
(*env)->DeleteLocalRef(env, ret);
|
(*env)->DeleteLocalRef(env, ret);
|
||||||
return 0;
|
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);
|
jbyteArray rxArr = (jbyteArray) (*env)->GetObjectField(env, ret, field_resp_data);
|
||||||
*rx_len = (*env)->GetArrayLength(env, rxArr);
|
*rx_len = (*env)->GetArrayLength(env, rxArr);
|
||||||
*rx = calloc(*rx_len, sizeof(uint8_t));
|
*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, txArr);
|
||||||
(*env)->DeleteLocalRef(env, rxArr);
|
(*env)->DeleteLocalRef(env, rxArr);
|
||||||
(*env)->DeleteLocalRef(env, headersArr);
|
(*env)->DeleteLocalRef(env, headersArr);
|
||||||
|
|
Loading…
Add table
Reference in a new issue