diff --git a/app-common/src/main/java/im/angry/openeuicc/core/DefaultEuiccChannelFactory.kt b/app-common/src/main/java/im/angry/openeuicc/core/DefaultEuiccChannelFactory.kt
index b249675..870baae 100644
--- a/app-common/src/main/java/im/angry/openeuicc/core/DefaultEuiccChannelFactory.kt
+++ b/app-common/src/main/java/im/angry/openeuicc/core/DefaultEuiccChannelFactory.kt
@@ -60,11 +60,11 @@ open class DefaultEuiccChannelFactory(protected val context: Context) : EuiccCha
Log.i(DefaultEuiccChannelManager.TAG, "Is OMAPI channel, setting MSS to 60")
it.lpa.setEs10xMss(60)
}
- } catch (e: IllegalArgumentException) {
+ } catch (_: IllegalArgumentException) {
// Failed
Log.w(
DefaultEuiccChannelManager.TAG,
- "OMAPI APDU interface unavailable for physical slot ${port.card.physicalSlotIndex}."
+ "OMAPI APDU interface unavailable for physical slot ${port.card.physicalSlotIndex} with ISD-R AID: ${isdrAid.encodeHex()}."
)
}
@@ -80,20 +80,29 @@ open class DefaultEuiccChannelFactory(protected val context: Context) : EuiccCha
if (bulkIn == null || bulkOut == null) return null
val conn = usbManager.openDevice(usbDevice) ?: return null
if (!conn.claimInterface(usbInterface, true)) return null
- return EuiccChannelImpl(
- context.getString(R.string.usb),
- FakeUiccPortInfoCompat(FakeUiccCardInfoCompat(EuiccChannelManager.USB_CHANNEL_ID)),
- intrinsicChannelName = usbDevice.productName,
- UsbApduInterface(
- conn,
- bulkIn,
- bulkOut,
- context.preferenceRepository.verboseLoggingFlow
- ),
- isdrAid,
- context.preferenceRepository.verboseLoggingFlow,
- context.preferenceRepository.ignoreTLSCertificateFlow,
- )
+ try {
+ return EuiccChannelImpl(
+ context.getString(R.string.usb),
+ FakeUiccPortInfoCompat(FakeUiccCardInfoCompat(EuiccChannelManager.USB_CHANNEL_ID)),
+ intrinsicChannelName = usbDevice.productName,
+ UsbApduInterface(
+ conn,
+ bulkIn,
+ bulkOut,
+ context.preferenceRepository.verboseLoggingFlow
+ ),
+ isdrAid,
+ context.preferenceRepository.verboseLoggingFlow,
+ context.preferenceRepository.ignoreTLSCertificateFlow,
+ )
+ } catch (_: IllegalArgumentException) {
+ // Failed
+ Log.w(
+ DefaultEuiccChannelManager.TAG,
+ "USB APDU interface unavailable for ISD-R AID: ${isdrAid.encodeHex()}."
+ )
+ }
+ return null
}
override fun cleanup() {
diff --git a/app-common/src/main/java/im/angry/openeuicc/core/DefaultEuiccChannelManager.kt b/app-common/src/main/java/im/angry/openeuicc/core/DefaultEuiccChannelManager.kt
index 74ec285..ac9ba08 100644
--- a/app-common/src/main/java/im/angry/openeuicc/core/DefaultEuiccChannelManager.kt
+++ b/app-common/src/main/java/im/angry/openeuicc/core/DefaultEuiccChannelManager.kt
@@ -277,11 +277,7 @@ open class DefaultEuiccChannelManager(
)
try {
val channel = tryOpenChannelFirstValidAid {
- euiccChannelFactory.tryOpenUsbEuiccChannel(
- device,
- iface,
- it
- )
+ euiccChannelFactory.tryOpenUsbEuiccChannel(device, iface, it)
}
if (channel != null && channel.lpa.valid) {
usbChannel = channel
diff --git a/app-common/src/main/java/im/angry/openeuicc/core/usb/UsbApduInterface.kt b/app-common/src/main/java/im/angry/openeuicc/core/usb/UsbApduInterface.kt
index f9e764b..107395f 100644
--- a/app-common/src/main/java/im/angry/openeuicc/core/usb/UsbApduInterface.kt
+++ b/app-common/src/main/java/im/angry/openeuicc/core/usb/UsbApduInterface.kt
@@ -45,6 +45,15 @@ class UsbApduInterface(
e.printStackTrace()
throw e
}
+
+ // Send Terminal Capabilities
+ // Specs: ETSI TS 102 221 v15.0.0 - 11.1.19 TERMINAL CAPABILITY
+ val terminalCapabilities = buildCmd(
+ 0x80.toByte(), 0xaa.toByte(), 0x00, 0x00,
+ "A9088100820101830107".decodeHex(),
+ le = null,
+ )
+ transmitApduByChannel(terminalCapabilities, 0)
}
override fun disconnect() {
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 4e499dc..aa922be 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
@@ -104,7 +104,7 @@ class EuiccInfoActivity : BaseEuiccAccessActivity(), OpenEuiccContextMarker {
add(Item(R.string.euicc_info_eid, channel.lpa.eID, copiedToastResId = R.string.toast_eid_copied))
channel.tryParseEuiccVendorInfo()?.let { vendorInfo ->
vendorInfo.skuName?.let { add(Item(R.string.euicc_info_sku, it)) }
- vendorInfo.serialNumber?.let { add(Item(R.string.euicc_info_sn, it)) }
+ vendorInfo.serialNumber?.let { add(Item(R.string.euicc_info_sn, it, copiedToastResId = R.string.toast_sn_copied)) }
vendorInfo.firmwareVersion?.let { add(Item(R.string.euicc_info_fw_ver, it)) }
vendorInfo.bootloaderVersion?.let { add(Item(R.string.euicc_info_bl_ver, it)) }
}
diff --git a/app-common/src/main/java/im/angry/openeuicc/util/LPAString.kt b/app-common/src/main/java/im/angry/openeuicc/util/LPAString.kt
index 20956fb..63a81f1 100644
--- a/app-common/src/main/java/im/angry/openeuicc/util/LPAString.kt
+++ b/app-common/src/main/java/im/angry/openeuicc/util/LPAString.kt
@@ -8,15 +8,15 @@ data class LPAString(
) {
companion object {
fun parse(input: String): LPAString {
- val components = input.removePrefix("LPA:").split('$')
- if (components.size < 2 || components[0] != "1") {
- throw IllegalArgumentException("Invalid activation code format")
- }
+ var token = input
+ if (token.startsWith("LPA:", ignoreCase = true)) token = token.drop(4)
+ val components = token.split('$').map { it.trim().ifBlank { null } }
+ require(components.getOrNull(0) == "1") { "Invalid AC_Format" }
return LPAString(
- address = components[1].trim(),
- matchingId = components.getOrNull(2)?.trim()?.ifBlank { null },
- oid = components.getOrNull(3)?.trim()?.ifBlank { null },
- confirmationCodeRequired = components.getOrNull(4)?.trim() == "1"
+ requireNotNull(components.getOrNull(1)) { "SM-DP+ is required" },
+ components.getOrNull(2),
+ components.getOrNull(3),
+ components.getOrNull(4) == "1"
)
}
}
diff --git a/app-common/src/main/java/im/angry/openeuicc/util/PreferenceUtils.kt b/app-common/src/main/java/im/angry/openeuicc/util/PreferenceUtils.kt
index 928079f..5f4aec4 100644
--- a/app-common/src/main/java/im/angry/openeuicc/util/PreferenceUtils.kt
+++ b/app-common/src/main/java/im/angry/openeuicc/util/PreferenceUtils.kt
@@ -45,11 +45,22 @@ const val EUICC_DEFAULT_ISDR_AID = "A0000005591010FFFFFFFF8900000100"
internal object PreferenceConstants {
val DEFAULT_AID_LIST = """
# One AID per line. Comment lines start with #.
+ # Refs:
+
# eUICC standard
$EUICC_DEFAULT_ISDR_AID
-
- # 5ber
+
+ # eSTK.me
+ A06573746B6D65FFFFFFFF4953442D52
+
+ # eSIM.me
+ A0000005591010000000008900000300
+
+ # 5ber.eSIM
A0000005591010FFFFFFFF8900050500
+
+ # Xesim
+ A0000005591010FFFFFFFF8900000177
""".trimIndent()
}
@@ -97,13 +108,12 @@ class PreferenceFlowWrapper private constructor(
defaultValue: T,
encoder: (T) -> T,
decoder: (T) -> T
- ) :
- this(
- context,
- key,
- context.dataStore.data.map { it[key]?.let(decoder) ?: defaultValue },
- encoder
- )
+ ) : this(
+ context,
+ key,
+ context.dataStore.data.map { it[key]?.let(decoder) ?: defaultValue },
+ encoder
+ )
suspend fun updatePreference(value: T) {
context.dataStore.edit { it[key] = encoder(value) }
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 9f993a3..079853e 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
@@ -34,15 +34,12 @@ fun formatFreeSpace(size: Int): String =
* If none is found, at least EUICC_DEFAULT_ISDR_AID is returned
*/
fun parseIsdrAidList(s: String): List =
- s.split('\n').map(String::trim).filter { !it.startsWith('#') }
+ s.split('\n')
.map(String::trim)
- .mapNotNull {
- try {
- it.decodeHex()
- } catch (_: IllegalArgumentException) {
- null
- }
- }
+ .filter { !it.startsWith('#') }
+ .map(String::trim)
+ .filter(String::isNotEmpty)
+ .mapNotNull { runCatching(it::decodeHex).getOrNull() }
.ifEmpty { listOf(EUICC_DEFAULT_ISDR_AID.decodeHex()) }
fun String.prettyPrintJson(): String {
diff --git a/app-common/src/main/res/layout/activity_isdr_aid_list.xml b/app-common/src/main/res/layout/activity_isdr_aid_list.xml
index 06a75a1..48135fb 100644
--- a/app-common/src/main/res/layout/activity_isdr_aid_list.xml
+++ b/app-common/src/main/res/layout/activity_isdr_aid_list.xml
@@ -11,6 +11,7 @@
android:id="@+id/isdr_aid_list_editor"
android:layout_width="0dp"
android:layout_height="0dp"
+ android:fontFamily="monospace"
android:importantForAutofill="no"
android:inputType="textMultiLine"
android:gravity="top|start"
diff --git a/app/src/main/java/im/angry/openeuicc/core/PrivilegedEuiccChannelFactory.kt b/app/src/main/java/im/angry/openeuicc/core/PrivilegedEuiccChannelFactory.kt
index 5489bd5..68eddef 100644
--- a/app/src/main/java/im/angry/openeuicc/core/PrivilegedEuiccChannelFactory.kt
+++ b/app/src/main/java/im/angry/openeuicc/core/PrivilegedEuiccChannelFactory.kt
@@ -44,11 +44,11 @@ class PrivilegedEuiccChannelFactory(context: Context) : DefaultEuiccChannelFacto
context.preferenceRepository.verboseLoggingFlow,
context.preferenceRepository.ignoreTLSCertificateFlow,
)
- } catch (e: IllegalArgumentException) {
+ } catch (_: IllegalArgumentException) {
// Failed
Log.w(
DefaultEuiccChannelManager.TAG,
- "TelephonyManager APDU interface unavailable for slot ${port.card.physicalSlotIndex} port ${port.portIndex}, falling back"
+ "TelephonyManager APDU interface unavailable for slot ${port.card.physicalSlotIndex} port ${port.portIndex} with ISD-R AID: ${isdrAid.encodeHex()}."
)
}
}