From 66bee041a01baae4205c33cdcb75dcf7905e7bd7 Mon Sep 17 00:00:00 2001 From: Peter Cai Date: Wed, 18 Dec 2024 20:04:55 -0500 Subject: [PATCH 01/11] ui: wizard: IMEI input type should be numberPassword --- app-common/src/main/res/layout/fragment_download_details.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app-common/src/main/res/layout/fragment_download_details.xml b/app-common/src/main/res/layout/fragment_download_details.xml index be01ad2..1a25075 100644 --- a/app-common/src/main/res/layout/fragment_download_details.xml +++ b/app-common/src/main/res/layout/fragment_download_details.xml @@ -82,7 +82,7 @@ android:maxLines="1" android:layout_width="match_parent" android:layout_height="match_parent" - android:inputType="textPassword" /> + android:inputType="numberPassword" /> From 6e3176668a2e6088826d528a54e2b84f83a9789c Mon Sep 17 00:00:00 2001 From: Peter Cai Date: Wed, 18 Dec 2024 20:44:08 -0500 Subject: [PATCH 02/11] core: Reconnect to USB readers after switching profiles as well Apparently some reader + card combination results in the need to re-establish the ISD-R even though this is not a modem. It doesn't hurt to run waitForReconnect() anyway :) --- .../core/DefaultEuiccChannelManager.kt | 28 +++++++++++++------ .../service/EuiccChannelManagerService.kt | 2 +- .../openeuicc/ui/EuiccManagementFragment.kt | 6 +--- 3 files changed, 21 insertions(+), 15 deletions(-) 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 07db80b..293042c 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 @@ -184,20 +184,30 @@ open class DefaultEuiccChannelManager( } override suspend fun waitForReconnect(physicalSlotId: Int, portId: Int, timeoutMillis: Long) { - if (physicalSlotId == EuiccChannelManager.USB_CHANNEL_ID) return - - // If there is already a valid channel, we close it proactively - // Sometimes the current channel can linger on for a bit even after it should have become invalid - channelCache.find { it.slotId == physicalSlotId && it.portId == portId }?.apply { - if (valid) close() + if (physicalSlotId == EuiccChannelManager.USB_CHANNEL_ID) { + usbChannel?.close() + usbChannel = null + } else { + // If there is already a valid channel, we close it proactively + // Sometimes the current channel can linger on for a bit even after it should have become invalid + channelCache.find { it.slotId == physicalSlotId && it.portId == portId }?.apply { + if (valid) close() + } } withTimeout(timeoutMillis) { while (true) { try { - // tryOpenEuiccChannel() will automatically dispose of invalid channels - // and recreate when needed - val channel = findEuiccChannelByPort(physicalSlotId, portId)!! + val channel = if (physicalSlotId == EuiccChannelManager.USB_CHANNEL_ID) { + // tryOpenUsbEuiccChannel() will always try to reopen the channel, even if + // a USB channel already exists + tryOpenUsbEuiccChannel() + usbChannel!! + } else { + // tryOpenEuiccChannel() will automatically dispose of invalid channels + // and recreate when needed + findEuiccChannelByPort(physicalSlotId, portId)!! + } check(channel.valid) { "Invalid channel" } break } catch (e: Exception) { diff --git a/app-common/src/main/java/im/angry/openeuicc/service/EuiccChannelManagerService.kt b/app-common/src/main/java/im/angry/openeuicc/service/EuiccChannelManagerService.kt index f76f1dc..da1062a 100644 --- a/app-common/src/main/java/im/angry/openeuicc/service/EuiccChannelManagerService.kt +++ b/app-common/src/main/java/im/angry/openeuicc/service/EuiccChannelManagerService.kt @@ -448,7 +448,7 @@ class EuiccChannelManagerService : LifecycleService(), OpenEuiccContextMarker { portId: Int, iccid: String, enable: Boolean, // Enable or disable the profile indicated in iccid - reconnectTimeoutMillis: Long = 0 // 0 = do not wait for reconnect, useful for USB readers + reconnectTimeoutMillis: Long = 0 // 0 = do not wait for reconnect ): ForegroundTaskSubscriberFlow = launchForegroundTask( getString(R.string.task_profile_switch), 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 f806ae0..842f4ec 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 @@ -228,11 +228,7 @@ open class EuiccManagementFragment : Fragment(), EuiccProfilesChangedListener, portId, iccid, enable, - reconnectTimeoutMillis = if (isUsb) { - 0 - } else { - 30 * 1000 - } + reconnectTimeoutMillis = 30 * 1000 ).waitDone() when (err) { From c0a691764515c8a7f1e94d32f7e251eec5ffdae3 Mon Sep 17 00:00:00 2001 From: Peter Cai Date: Wed, 18 Dec 2024 20:57:37 -0500 Subject: [PATCH 03/11] Remove unused isForegroundTaskRunning idk what this was used for --- .../im/angry/openeuicc/service/EuiccChannelManagerService.kt | 3 --- 1 file changed, 3 deletions(-) diff --git a/app-common/src/main/java/im/angry/openeuicc/service/EuiccChannelManagerService.kt b/app-common/src/main/java/im/angry/openeuicc/service/EuiccChannelManagerService.kt index da1062a..760f1af 100644 --- a/app-common/src/main/java/im/angry/openeuicc/service/EuiccChannelManagerService.kt +++ b/app-common/src/main/java/im/angry/openeuicc/service/EuiccChannelManagerService.kt @@ -362,9 +362,6 @@ class EuiccChannelManagerService : LifecycleService(), OpenEuiccContextMarker { ) } - val isForegroundTaskRunning: Boolean - get() = foregroundTaskState.value != ForegroundTaskState.Idle - suspend fun waitForForegroundTask() { foregroundTaskState.takeWhile { it != ForegroundTaskState.Idle } .collect() From 0f8749ee04de7ec65180aa88801a48eb33317705 Mon Sep 17 00:00:00 2001 From: Peter Cai Date: Wed, 18 Dec 2024 21:11:15 -0500 Subject: [PATCH 04/11] ui: wizard: Verify the EuiccChannel is still valid every time next is pressed --- .../ui/wizard/DownloadWizardActivity.kt | 44 ++++++++++++++++--- app-common/src/main/res/values/strings.xml | 1 + 2 files changed, 38 insertions(+), 7 deletions(-) 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 66b31bc..d2310b3 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 @@ -5,15 +5,21 @@ import android.view.View import android.view.inputmethod.InputMethodManager import android.widget.Button import android.widget.ProgressBar +import android.widget.Toast import androidx.activity.OnBackPressedCallback import androidx.activity.enableEdgeToEdge import androidx.core.view.ViewCompat import androidx.core.view.WindowInsetsCompat import androidx.core.view.updatePadding import androidx.fragment.app.Fragment +import androidx.lifecycle.lifecycleScope import im.angry.openeuicc.common.R +import im.angry.openeuicc.core.EuiccChannelManager import im.angry.openeuicc.ui.BaseEuiccAccessActivity import im.angry.openeuicc.util.* +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext import net.typeblog.lpac_jni.LocalProfileAssistant class DownloadWizardActivity: BaseEuiccAccessActivity() { @@ -149,13 +155,37 @@ class DownloadWizardActivity: BaseEuiccAccessActivity() { private fun onNextPressed() { hideIme() - if (currentFragment?.hasNext == true) { - currentFragment?.beforeNext() - val nextFrag = currentFragment?.createNextFragment() - if (nextFrag == null) { - finish() - } else { - showFragment(nextFrag, R.anim.slide_in_right, R.anim.slide_out_left) + progressBar.visibility = View.VISIBLE + progressBar.isIndeterminate = true + + lifecycleScope.launch(Dispatchers.Main) { + if (state.selectedLogicalSlot >= 0) { + try { + // This is run on IO by default + euiccChannelManager.withEuiccChannel(state.selectedLogicalSlot) { channel -> + // Be _very_ sure that the channel we got is valid + if (!channel.valid) throw EuiccChannelManager.EuiccChannelNotFoundException() + } + } catch (e: EuiccChannelManager.EuiccChannelNotFoundException) { + Toast.makeText( + this@DownloadWizardActivity, + R.string.download_wizard_slot_removed, + Toast.LENGTH_LONG + ).show() + finish() + } + } + + progressBar.visibility = View.GONE + + if (currentFragment?.hasNext == true) { + currentFragment?.beforeNext() + val nextFrag = currentFragment?.createNextFragment() + if (nextFrag == null) { + finish() + } else { + showFragment(nextFrag, R.anim.slide_in_right, R.anim.slide_out_left) + } } } } diff --git a/app-common/src/main/res/values/strings.xml b/app-common/src/main/res/values/strings.xml index 7b0ff69..6c9fc34 100644 --- a/app-common/src/main/res/values/strings.xml +++ b/app-common/src/main/res/values/strings.xml @@ -61,6 +61,7 @@ Download Wizard Back Next + Selected SIM has been removed Select or confirm the eSIM you would like to download to: Type: Removable From 653a7b32ee8cc6d5c2294f69df49f137dc941644 Mon Sep 17 00:00:00 2001 From: Peter Cai Date: Wed, 18 Dec 2024 21:13:08 -0500 Subject: [PATCH 05/11] ui: wizard: Prevent clicking next multiple times --- .../java/im/angry/openeuicc/ui/wizard/DownloadWizardActivity.kt | 2 ++ 1 file changed, 2 insertions(+) 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 d2310b3..e342dee 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 @@ -155,6 +155,7 @@ class DownloadWizardActivity: BaseEuiccAccessActivity() { private fun onNextPressed() { hideIme() + nextButton.isEnabled = false progressBar.visibility = View.VISIBLE progressBar.isIndeterminate = true @@ -177,6 +178,7 @@ class DownloadWizardActivity: BaseEuiccAccessActivity() { } progressBar.visibility = View.GONE + nextButton.isEnabled = true if (currentFragment?.hasNext == true) { currentFragment?.beforeNext() From e7ef370e461a0f0511b95b868902a1df514a64f2 Mon Sep 17 00:00:00 2001 From: septs Date: Fri, 20 Dec 2024 22:56:31 +0100 Subject: [PATCH 06/11] feat: sgp.22 version in euicc info activity (#130) Co-authored-by: Peter Cai Reviewed-on: https://gitea.angry.im/PeterCxy/OpenEUICC/pulls/130 Co-authored-by: septs Co-committed-by: septs --- .../src/main/java/im/angry/openeuicc/ui/EuiccInfoActivity.kt | 1 + app-common/src/main/res/values/strings.xml | 1 + libs/lpac-jni/src/main/java/net/typeblog/lpac_jni/EuiccInfo2.kt | 1 + libs/lpac-jni/src/main/java/net/typeblog/lpac_jni/LpacJni.kt | 1 + .../java/net/typeblog/lpac_jni/impl/LocalProfileAssistantImpl.kt | 1 + libs/lpac-jni/src/main/jni/lpac-jni/lpac-jni.c | 1 + 6 files changed, 6 insertions(+) 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 aca2572..df206c8 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 @@ -114,6 +114,7 @@ class EuiccInfoActivity : BaseEuiccAccessActivity(), OpenEuiccContextMarker { ) ) channel.lpa.euiccInfo2.let { info -> + add(Item(R.string.euicc_info_sgp22_version, info?.sgp22Version)) add(Item(R.string.euicc_info_firmware_version, info?.euiccFirmwareVersion)) add(Item(R.string.euicc_info_globalplatform_version, info?.globalPlatformVersion)) add(Item(R.string.euicc_info_pp_version, info?.ppVersion)) diff --git a/app-common/src/main/res/values/strings.xml b/app-common/src/main/res/values/strings.xml index 6c9fc34..6da5990 100644 --- a/app-common/src/main/res/values/strings.xml +++ b/app-common/src/main/res/values/strings.xml @@ -122,6 +122,7 @@ Access Mode Removable EID + SGP.22 Version eUICC OS Version GlobalPlatform Version SAS Accreditation Number diff --git a/libs/lpac-jni/src/main/java/net/typeblog/lpac_jni/EuiccInfo2.kt b/libs/lpac-jni/src/main/java/net/typeblog/lpac_jni/EuiccInfo2.kt index e69c7ff..6c73051 100644 --- a/libs/lpac-jni/src/main/java/net/typeblog/lpac_jni/EuiccInfo2.kt +++ b/libs/lpac-jni/src/main/java/net/typeblog/lpac_jni/EuiccInfo2.kt @@ -2,6 +2,7 @@ package net.typeblog.lpac_jni /* Corresponds to EuiccInfo2 in SGP.22 */ data class EuiccInfo2( + val sgp22Version: String, val profileVersion: String, val euiccFirmwareVersion: String, val globalPlatformVersion: String, diff --git a/libs/lpac-jni/src/main/java/net/typeblog/lpac_jni/LpacJni.kt b/libs/lpac-jni/src/main/java/net/typeblog/lpac_jni/LpacJni.kt index d50c1c1..370fcab 100644 --- a/libs/lpac-jni/src/main/java/net/typeblog/lpac_jni/LpacJni.kt +++ b/libs/lpac-jni/src/main/java/net/typeblog/lpac_jni/LpacJni.kt @@ -62,6 +62,7 @@ internal object LpacJni { external fun notificationsFree(head: Long) // EuiccInfo2 external fun euiccInfo2Free(info: Long) + external fun euiccInfo2GetSGP22Version(info: Long): String external fun euiccInfo2GetProfileVersion(info: Long): String external fun euiccInfo2GetEuiccFirmwareVersion(info: Long): String external fun euiccInfo2GetGlobalPlatformVersion(info: Long): String diff --git a/libs/lpac-jni/src/main/java/net/typeblog/lpac_jni/impl/LocalProfileAssistantImpl.kt b/libs/lpac-jni/src/main/java/net/typeblog/lpac_jni/impl/LocalProfileAssistantImpl.kt index 0330d82..7310acd 100644 --- a/libs/lpac-jni/src/main/java/net/typeblog/lpac_jni/impl/LocalProfileAssistantImpl.kt +++ b/libs/lpac-jni/src/main/java/net/typeblog/lpac_jni/impl/LocalProfileAssistantImpl.kt @@ -171,6 +171,7 @@ class LocalProfileAssistantImpl( } val ret = EuiccInfo2( + LpacJni.euiccInfo2GetSGP22Version(cInfo), LpacJni.euiccInfo2GetProfileVersion(cInfo), LpacJni.euiccInfo2GetEuiccFirmwareVersion(cInfo), LpacJni.euiccInfo2GetGlobalPlatformVersion(cInfo), diff --git a/libs/lpac-jni/src/main/jni/lpac-jni/lpac-jni.c b/libs/lpac-jni/src/main/jni/lpac-jni/lpac-jni.c index e438107..38d4f3a 100644 --- a/libs/lpac-jni/src/main/jni/lpac-jni/lpac-jni.c +++ b/libs/lpac-jni/src/main/jni/lpac-jni/lpac-jni.c @@ -266,6 +266,7 @@ void lpac_jni_euiccinfo2_free(struct es10c_ex_euiccinfo2 *info) { LPAC_JNI_STRUCT_GETTER_NULL_TERM_LIST_NEXT(char*, stringArr) LPAC_JNI_STRUCT_FREE(struct es10c_ex_euiccinfo2, euiccInfo2, lpac_jni_euiccinfo2_free) +LPAC_JNI_STRUCT_GETTER_STRING(struct es10c_ex_euiccinfo2, euiccInfo2, svn, SGP22Version) LPAC_JNI_STRUCT_GETTER_STRING(struct es10c_ex_euiccinfo2, euiccInfo2, profileVersion, ProfileVersion) LPAC_JNI_STRUCT_GETTER_STRING(struct es10c_ex_euiccinfo2, euiccInfo2, euiccFirmwareVer, EuiccFirmwareVersion) LPAC_JNI_STRUCT_GETTER_STRING(struct es10c_ex_euiccinfo2, euiccInfo2, globalplatformVersion, GlobalPlatformVersion) From 31d595a6b19f54d6bb70a04f561d41f8d80f77f6 Mon Sep 17 00:00:00 2001 From: Peter Cai Date: Fri, 20 Dec 2024 19:03:28 -0500 Subject: [PATCH 07/11] ui: Don't reset profile name while resuming the rename fragment --- .../java/im/angry/openeuicc/ui/ProfileRenameFragment.kt | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/app-common/src/main/java/im/angry/openeuicc/ui/ProfileRenameFragment.kt b/app-common/src/main/java/im/angry/openeuicc/ui/ProfileRenameFragment.kt index e3f2d8d..25c5273 100644 --- a/app-common/src/main/java/im/angry/openeuicc/ui/ProfileRenameFragment.kt +++ b/app-common/src/main/java/im/angry/openeuicc/ui/ProfileRenameFragment.kt @@ -54,6 +54,7 @@ class ProfileRenameFragment : BaseMaterialDialogFragment(), EuiccChannelFragment override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) + profileRenameNewName.editText!!.setText(requireArguments().getString("currentName")) toolbar.apply { setTitle(R.string.rename) setNavigationOnClickListener { @@ -66,11 +67,6 @@ class ProfileRenameFragment : BaseMaterialDialogFragment(), EuiccChannelFragment } } - override fun onStart() { - super.onStart() - profileRenameNewName.editText!!.setText(requireArguments().getString("currentName")) - } - override fun onResume() { super.onResume() setWidthPercent(95) From 3ef78a23dbf26ebd20bf26478db209d4620b3fec Mon Sep 17 00:00:00 2001 From: septs Date: Fri, 20 Dec 2024 19:05:29 -0500 Subject: [PATCH 08/11] feat: atr in euiccinfo activity commit 0fbec512ab7dd8be207bb771129a29eb5f9434a8 Author: septs Date: Wed Dec 18 21:27:53 2024 +0800 feat: atr in euiccinfo activity --- .../openeuicc/core/LocalProfileAssistantWrapper.kt | 2 ++ .../im/angry/openeuicc/core/OmapiApduInterface.kt | 3 +++ .../angry/openeuicc/core/usb/UsbApduInterface.kt | 9 ++++++++- .../im/angry/openeuicc/ui/EuiccInfoActivity.kt | 14 +++++++++++++- app-common/src/main/res/values/strings.xml | 3 +++ .../core/TelephonyManagerApduInterface.kt | 3 +++ .../java/net/typeblog/lpac_jni/ApduInterface.kt | 5 +++++ .../net/typeblog/lpac_jni/LocalProfileAssistant.kt | 2 ++ .../lpac_jni/impl/LocalProfileAssistantImpl.kt | 2 ++ 9 files changed, 41 insertions(+), 2 deletions(-) diff --git a/app-common/src/main/java/im/angry/openeuicc/core/LocalProfileAssistantWrapper.kt b/app-common/src/main/java/im/angry/openeuicc/core/LocalProfileAssistantWrapper.kt index b715ca0..7f35213 100644 --- a/app-common/src/main/java/im/angry/openeuicc/core/LocalProfileAssistantWrapper.kt +++ b/app-common/src/main/java/im/angry/openeuicc/core/LocalProfileAssistantWrapper.kt @@ -30,6 +30,8 @@ class LocalProfileAssistantWrapper(orig: LocalProfileAssistant) : override val euiccInfo2: EuiccInfo2? get() = lpa.euiccInfo2 + override fun readATR() = lpa.readATR() + override fun setEs10xMss(mss: Byte) = lpa.setEs10xMss(mss) override fun enableProfile(iccid: String, refresh: Boolean): Boolean = diff --git a/app-common/src/main/java/im/angry/openeuicc/core/OmapiApduInterface.kt b/app-common/src/main/java/im/angry/openeuicc/core/OmapiApduInterface.kt index 71aa386..c024b98 100644 --- a/app-common/src/main/java/im/angry/openeuicc/core/OmapiApduInterface.kt +++ b/app-common/src/main/java/im/angry/openeuicc/core/OmapiApduInterface.kt @@ -26,6 +26,9 @@ class OmapiApduInterface( override val valid: Boolean get() = service.isConnected && (this::session.isInitialized && !session.isClosed) + override fun readATR() = + session.atr?.clone() ?: throw IllegalStateException("atr unavailable") + override fun connect() { session = service.getUiccReaderCompat(port.logicalSlotIndex + 1).openSession() } 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 9894343..da103e8 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 @@ -32,7 +32,9 @@ class UsbApduInterface( transceiver = UsbCcidTransceiver(conn, bulkIn, bulkOut, ccidDescription, verboseLoggingFlow) try { - transceiver.iccPowerOn() + // 6.1.1.1 PC_to_RDR_IccPowerOn (Page 20 of 40) + // https://www.usb.org/sites/default/files/DWG_Smart-Card_USB-ICC_ICCD_rev10.pdf + atr = transceiver.iccPowerOn().data } catch (e: Exception) { e.printStackTrace() throw e @@ -99,6 +101,11 @@ class UsbApduInterface( override val valid: Boolean get() = channelId != -1 + private var atr: ByteArray? = null + + override fun readATR() = + atr?.clone() ?: throw IllegalStateException("atr unavailable") + private fun isSuccessResponse(resp: ByteArray): Boolean = resp.size >= 2 && resp[resp.size - 2] == 0x90.toByte() && resp[resp.size - 1] == 0x00.toByte() 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 df206c8..a36314c 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 @@ -23,6 +23,7 @@ import im.angry.openeuicc.common.R import im.angry.openeuicc.core.EuiccChannel import im.angry.openeuicc.core.EuiccChannelManager import im.angry.openeuicc.util.* +import kotlinx.coroutines.flow.first import kotlinx.coroutines.launch import net.typeblog.lpac_jni.impl.PKID_GSMA_LIVE_CI import net.typeblog.lpac_jni.impl.PKID_GSMA_TEST_CI @@ -41,7 +42,7 @@ class EuiccInfoActivity : BaseEuiccAccessActivity(), OpenEuiccContextMarker { @StringRes val titleResId: Int, val content: String?, - val copiedToastResId: Int? = null + val copiedToastResId: Int? = null, ) override fun onCreate(savedInstanceState: Bundle?) { @@ -134,6 +135,17 @@ class EuiccInfoActivity : BaseEuiccAccessActivity(), OpenEuiccContextMarker { } add(Item(R.string.euicc_info_ci_type, getString(resId))) } + add( + Item( + R.string.euicc_info_atr, + try { + channel.lpa.readATR().encodeHex() + } catch (e: Exception) { + getString(R.string.euicc_info_atr_unavailable) + }, + copiedToastResId = R.string.toast_atr_copied, + ) + ) } private fun formatByBoolean(b: Boolean, res: Pair): String = diff --git a/app-common/src/main/res/values/strings.xml b/app-common/src/main/res/values/strings.xml index 6da5990..4723e88 100644 --- a/app-common/src/main/res/values/strings.xml +++ b/app-common/src/main/res/values/strings.xml @@ -31,6 +31,7 @@ Confirmation string mismatch ICCID copied to clipboard EID copied to clipboard + ATR copied to clipboard Grant USB permission Permission is needed to access the USB smart card reader. @@ -132,6 +133,8 @@ GSMA Live CI GSMA Test CI Unknown eSIM CI + ATR (Answer To Reset) + ATR unavailable Yes No diff --git a/app/src/main/java/im/angry/openeuicc/core/TelephonyManagerApduInterface.kt b/app/src/main/java/im/angry/openeuicc/core/TelephonyManagerApduInterface.kt index 6b09368..6ab695d 100644 --- a/app/src/main/java/im/angry/openeuicc/core/TelephonyManagerApduInterface.kt +++ b/app/src/main/java/im/angry/openeuicc/core/TelephonyManagerApduInterface.kt @@ -25,6 +25,9 @@ class TelephonyManagerApduInterface( // just that transactions might return errors or nonsense get() = lastChannel != -1 + override fun readATR() = + throw IllegalStateException("atr unavailable") + override fun connect() { // Do nothing } diff --git a/libs/lpac-jni/src/main/java/net/typeblog/lpac_jni/ApduInterface.kt b/libs/lpac-jni/src/main/java/net/typeblog/lpac_jni/ApduInterface.kt index dfa92df..4e52a42 100644 --- a/libs/lpac-jni/src/main/java/net/typeblog/lpac_jni/ApduInterface.kt +++ b/libs/lpac-jni/src/main/java/net/typeblog/lpac_jni/ApduInterface.kt @@ -16,4 +16,9 @@ interface ApduInterface { * callers should further check with the LPA to fully determine the validity of a channel */ val valid: Boolean + + /** + * Read Answer To Reset + */ + fun readATR(): ByteArray } \ No newline at end of file diff --git a/libs/lpac-jni/src/main/java/net/typeblog/lpac_jni/LocalProfileAssistant.kt b/libs/lpac-jni/src/main/java/net/typeblog/lpac_jni/LocalProfileAssistant.kt index 48ab1c5..52ffce2 100644 --- a/libs/lpac-jni/src/main/java/net/typeblog/lpac_jni/LocalProfileAssistant.kt +++ b/libs/lpac-jni/src/main/java/net/typeblog/lpac_jni/LocalProfileAssistant.kt @@ -23,6 +23,8 @@ interface LocalProfileAssistant { // Extended EuiccInfo for use with LUIs, containing information such as firmware version val euiccInfo2: EuiccInfo2? + fun readATR(): ByteArray + /** * Set the max segment size (mss) for all es10x commands. This can help with removable * eUICCs that may run at a baud rate too fast for the modem. diff --git a/libs/lpac-jni/src/main/java/net/typeblog/lpac_jni/impl/LocalProfileAssistantImpl.kt b/libs/lpac-jni/src/main/java/net/typeblog/lpac_jni/impl/LocalProfileAssistantImpl.kt index 7310acd..bf48482 100644 --- a/libs/lpac-jni/src/main/java/net/typeblog/lpac_jni/impl/LocalProfileAssistantImpl.kt +++ b/libs/lpac-jni/src/main/java/net/typeblog/lpac_jni/impl/LocalProfileAssistantImpl.kt @@ -188,6 +188,8 @@ class LocalProfileAssistantImpl( return ret } + override fun readATR() = apduInterface.readATR() + @Synchronized override fun enableProfile(iccid: String, refresh: Boolean): Boolean = LpacJni.es10cEnableProfile(contextHandle, iccid, refresh) == 0 From 6b4723daeed139404946997857e8c6b3f609ce0d Mon Sep 17 00:00:00 2001 From: Peter Cai Date: Fri, 20 Dec 2024 19:30:33 -0500 Subject: [PATCH 09/11] refactor: ATR should not be the concern of lpac-jni ...instead, use a separate interface to represent channel types that do support reading ATR. --- .../angry/openeuicc/core/ApduInterfaceAtrProvider.kt | 5 +++++ .../main/java/im/angry/openeuicc/core/EuiccChannel.kt | 5 +++++ .../java/im/angry/openeuicc/core/EuiccChannelImpl.kt | 5 ++++- .../im/angry/openeuicc/core/EuiccChannelWrapper.kt | 2 ++ .../openeuicc/core/LocalProfileAssistantWrapper.kt | 2 -- .../java/im/angry/openeuicc/core/OmapiApduInterface.kt | 6 +++--- .../im/angry/openeuicc/core/usb/UsbApduInterface.kt | 10 ++++------ .../java/im/angry/openeuicc/ui/EuiccInfoActivity.kt | 7 +------ .../openeuicc/core/TelephonyManagerApduInterface.kt | 3 --- .../main/java/net/typeblog/lpac_jni/ApduInterface.kt | 5 ----- .../net/typeblog/lpac_jni/LocalProfileAssistant.kt | 2 -- .../lpac_jni/impl/LocalProfileAssistantImpl.kt | 2 -- 12 files changed, 24 insertions(+), 30 deletions(-) create mode 100644 app-common/src/main/java/im/angry/openeuicc/core/ApduInterfaceAtrProvider.kt diff --git a/app-common/src/main/java/im/angry/openeuicc/core/ApduInterfaceAtrProvider.kt b/app-common/src/main/java/im/angry/openeuicc/core/ApduInterfaceAtrProvider.kt new file mode 100644 index 0000000..c3646d2 --- /dev/null +++ b/app-common/src/main/java/im/angry/openeuicc/core/ApduInterfaceAtrProvider.kt @@ -0,0 +1,5 @@ +package im.angry.openeuicc.core + +interface ApduInterfaceAtrProvider { + val atr: ByteArray? +} \ No newline at end of file diff --git a/app-common/src/main/java/im/angry/openeuicc/core/EuiccChannel.kt b/app-common/src/main/java/im/angry/openeuicc/core/EuiccChannel.kt index 541f867..5f399ea 100644 --- a/app-common/src/main/java/im/angry/openeuicc/core/EuiccChannel.kt +++ b/app-common/src/main/java/im/angry/openeuicc/core/EuiccChannel.kt @@ -16,6 +16,11 @@ interface EuiccChannel { val valid: Boolean + /** + * Answer to Reset (ATR) value of the underlying interface, if any + */ + val atr: ByteArray? + /** * Intrinsic name of this channel. For device-internal SIM slots, * this should be null; for USB readers, this should be the name of diff --git a/app-common/src/main/java/im/angry/openeuicc/core/EuiccChannelImpl.kt b/app-common/src/main/java/im/angry/openeuicc/core/EuiccChannelImpl.kt index a281948..a82cb97 100644 --- a/app-common/src/main/java/im/angry/openeuicc/core/EuiccChannelImpl.kt +++ b/app-common/src/main/java/im/angry/openeuicc/core/EuiccChannelImpl.kt @@ -11,7 +11,7 @@ class EuiccChannelImpl( override val type: String, override val port: UiccPortInfoCompat, override val intrinsicChannelName: String?, - apduInterface: ApduInterface, + private val apduInterface: ApduInterface, verboseLoggingFlow: Flow, ignoreTLSCertificateFlow: Flow ) : EuiccChannel { @@ -22,6 +22,9 @@ class EuiccChannelImpl( override val lpa: LocalProfileAssistant = LocalProfileAssistantImpl(apduInterface, HttpInterfaceImpl(verboseLoggingFlow, ignoreTLSCertificateFlow)) + override val atr: ByteArray? + get() = (apduInterface as? ApduInterfaceAtrProvider)?.atr + override val valid: Boolean get() = lpa.valid diff --git a/app-common/src/main/java/im/angry/openeuicc/core/EuiccChannelWrapper.kt b/app-common/src/main/java/im/angry/openeuicc/core/EuiccChannelWrapper.kt index 6011f53..4204e82 100644 --- a/app-common/src/main/java/im/angry/openeuicc/core/EuiccChannelWrapper.kt +++ b/app-common/src/main/java/im/angry/openeuicc/core/EuiccChannelWrapper.kt @@ -33,6 +33,8 @@ class EuiccChannelWrapper(orig: EuiccChannel) : EuiccChannel { get() = channel.valid override val intrinsicChannelName: String? get() = channel.intrinsicChannelName + override val atr: ByteArray? + get() = channel.atr override fun close() = channel.close() diff --git a/app-common/src/main/java/im/angry/openeuicc/core/LocalProfileAssistantWrapper.kt b/app-common/src/main/java/im/angry/openeuicc/core/LocalProfileAssistantWrapper.kt index 7f35213..b715ca0 100644 --- a/app-common/src/main/java/im/angry/openeuicc/core/LocalProfileAssistantWrapper.kt +++ b/app-common/src/main/java/im/angry/openeuicc/core/LocalProfileAssistantWrapper.kt @@ -30,8 +30,6 @@ class LocalProfileAssistantWrapper(orig: LocalProfileAssistant) : override val euiccInfo2: EuiccInfo2? get() = lpa.euiccInfo2 - override fun readATR() = lpa.readATR() - override fun setEs10xMss(mss: Byte) = lpa.setEs10xMss(mss) override fun enableProfile(iccid: String, refresh: Boolean): Boolean = diff --git a/app-common/src/main/java/im/angry/openeuicc/core/OmapiApduInterface.kt b/app-common/src/main/java/im/angry/openeuicc/core/OmapiApduInterface.kt index c024b98..c70669d 100644 --- a/app-common/src/main/java/im/angry/openeuicc/core/OmapiApduInterface.kt +++ b/app-common/src/main/java/im/angry/openeuicc/core/OmapiApduInterface.kt @@ -15,7 +15,7 @@ class OmapiApduInterface( private val service: SEService, private val port: UiccPortInfoCompat, private val verboseLoggingFlow: Flow -): ApduInterface { +): ApduInterface, ApduInterfaceAtrProvider { companion object { const val TAG = "OmapiApduInterface" } @@ -26,8 +26,8 @@ class OmapiApduInterface( override val valid: Boolean get() = service.isConnected && (this::session.isInitialized && !session.isClosed) - override fun readATR() = - session.atr?.clone() ?: throw IllegalStateException("atr unavailable") + override val atr: ByteArray? + get() = session.atr override fun connect() { session = service.getUiccReaderCompat(port.logicalSlotIndex + 1).openSession() 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 da103e8..624ef89 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 @@ -3,6 +3,7 @@ package im.angry.openeuicc.core.usb import android.hardware.usb.UsbDeviceConnection import android.hardware.usb.UsbEndpoint import android.util.Log +import im.angry.openeuicc.core.ApduInterfaceAtrProvider import im.angry.openeuicc.util.* import kotlinx.coroutines.flow.Flow import net.typeblog.lpac_jni.ApduInterface @@ -12,7 +13,7 @@ class UsbApduInterface( private val bulkIn: UsbEndpoint, private val bulkOut: UsbEndpoint, private val verboseLoggingFlow: Flow -): ApduInterface { +) : ApduInterface, ApduInterfaceAtrProvider { companion object { private const val TAG = "UsbApduInterface" } @@ -22,6 +23,8 @@ class UsbApduInterface( private var channelId = -1 + override var atr: ByteArray? = null + override fun connect() { ccidDescription = UsbCcidDescription.fromRawDescriptors(conn.rawDescriptors)!! @@ -101,11 +104,6 @@ class UsbApduInterface( override val valid: Boolean get() = channelId != -1 - private var atr: ByteArray? = null - - override fun readATR() = - atr?.clone() ?: throw IllegalStateException("atr unavailable") - private fun isSuccessResponse(resp: ByteArray): Boolean = resp.size >= 2 && resp[resp.size - 2] == 0x90.toByte() && resp[resp.size - 1] == 0x00.toByte() 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 a36314c..735166d 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 @@ -23,7 +23,6 @@ import im.angry.openeuicc.common.R import im.angry.openeuicc.core.EuiccChannel import im.angry.openeuicc.core.EuiccChannelManager import im.angry.openeuicc.util.* -import kotlinx.coroutines.flow.first import kotlinx.coroutines.launch import net.typeblog.lpac_jni.impl.PKID_GSMA_LIVE_CI import net.typeblog.lpac_jni.impl.PKID_GSMA_TEST_CI @@ -138,11 +137,7 @@ class EuiccInfoActivity : BaseEuiccAccessActivity(), OpenEuiccContextMarker { add( Item( R.string.euicc_info_atr, - try { - channel.lpa.readATR().encodeHex() - } catch (e: Exception) { - getString(R.string.euicc_info_atr_unavailable) - }, + channel.atr?.encodeHex() ?: getString(R.string.euicc_info_atr_unavailable), copiedToastResId = R.string.toast_atr_copied, ) ) diff --git a/app/src/main/java/im/angry/openeuicc/core/TelephonyManagerApduInterface.kt b/app/src/main/java/im/angry/openeuicc/core/TelephonyManagerApduInterface.kt index 6ab695d..6b09368 100644 --- a/app/src/main/java/im/angry/openeuicc/core/TelephonyManagerApduInterface.kt +++ b/app/src/main/java/im/angry/openeuicc/core/TelephonyManagerApduInterface.kt @@ -25,9 +25,6 @@ class TelephonyManagerApduInterface( // just that transactions might return errors or nonsense get() = lastChannel != -1 - override fun readATR() = - throw IllegalStateException("atr unavailable") - override fun connect() { // Do nothing } diff --git a/libs/lpac-jni/src/main/java/net/typeblog/lpac_jni/ApduInterface.kt b/libs/lpac-jni/src/main/java/net/typeblog/lpac_jni/ApduInterface.kt index 4e52a42..dfa92df 100644 --- a/libs/lpac-jni/src/main/java/net/typeblog/lpac_jni/ApduInterface.kt +++ b/libs/lpac-jni/src/main/java/net/typeblog/lpac_jni/ApduInterface.kt @@ -16,9 +16,4 @@ interface ApduInterface { * callers should further check with the LPA to fully determine the validity of a channel */ val valid: Boolean - - /** - * Read Answer To Reset - */ - fun readATR(): ByteArray } \ No newline at end of file diff --git a/libs/lpac-jni/src/main/java/net/typeblog/lpac_jni/LocalProfileAssistant.kt b/libs/lpac-jni/src/main/java/net/typeblog/lpac_jni/LocalProfileAssistant.kt index 52ffce2..48ab1c5 100644 --- a/libs/lpac-jni/src/main/java/net/typeblog/lpac_jni/LocalProfileAssistant.kt +++ b/libs/lpac-jni/src/main/java/net/typeblog/lpac_jni/LocalProfileAssistant.kt @@ -23,8 +23,6 @@ interface LocalProfileAssistant { // Extended EuiccInfo for use with LUIs, containing information such as firmware version val euiccInfo2: EuiccInfo2? - fun readATR(): ByteArray - /** * Set the max segment size (mss) for all es10x commands. This can help with removable * eUICCs that may run at a baud rate too fast for the modem. diff --git a/libs/lpac-jni/src/main/java/net/typeblog/lpac_jni/impl/LocalProfileAssistantImpl.kt b/libs/lpac-jni/src/main/java/net/typeblog/lpac_jni/impl/LocalProfileAssistantImpl.kt index bf48482..7310acd 100644 --- a/libs/lpac-jni/src/main/java/net/typeblog/lpac_jni/impl/LocalProfileAssistantImpl.kt +++ b/libs/lpac-jni/src/main/java/net/typeblog/lpac_jni/impl/LocalProfileAssistantImpl.kt @@ -188,8 +188,6 @@ class LocalProfileAssistantImpl( return ret } - override fun readATR() = apduInterface.readATR() - @Synchronized override fun enableProfile(iccid: String, refresh: Boolean): Boolean = LpacJni.es10cEnableProfile(contextHandle, iccid, refresh) == 0 From 3a860601a34f8c48cb6669d50252d3787b87cdca Mon Sep 17 00:00:00 2001 From: Peter Cai Date: Fri, 20 Dec 2024 19:32:58 -0500 Subject: [PATCH 10/11] ui: Optimize ATR strings --- .../src/main/java/im/angry/openeuicc/ui/EuiccInfoActivity.kt | 2 +- app-common/src/main/res/values/strings.xml | 4 ++-- 2 files changed, 3 insertions(+), 3 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 735166d..b303e33 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 @@ -137,7 +137,7 @@ class EuiccInfoActivity : BaseEuiccAccessActivity(), OpenEuiccContextMarker { add( Item( R.string.euicc_info_atr, - channel.atr?.encodeHex() ?: getString(R.string.euicc_info_atr_unavailable), + channel.atr?.encodeHex() ?: getString(R.string.unavailable), copiedToastResId = R.string.toast_atr_copied, ) ) diff --git a/app-common/src/main/res/values/strings.xml b/app-common/src/main/res/values/strings.xml index 4723e88..bc46825 100644 --- a/app-common/src/main/res/values/strings.xml +++ b/app-common/src/main/res/values/strings.xml @@ -3,6 +3,7 @@ No removable eUICC card accessible by this app is detected on this device. Insert a compatible card or a USB reader. No profiles (yet) on this eSIM. Unknown + Unavailable Help Reload Slots @@ -133,8 +134,7 @@ GSMA Live CI GSMA Test CI Unknown eSIM CI - ATR (Answer To Reset) - ATR unavailable + Answer To Reset (ATR) Yes No From d0b3d54c6662be0f6c7e8668ff7389f825d26956 Mon Sep 17 00:00:00 2001 From: Peter Cai Date: Fri, 20 Dec 2024 19:33:43 -0500 Subject: [PATCH 11/11] ui: Allow multi-line strings in EuiccInfoActivity --- app-common/src/main/res/layout/euicc_info_item.xml | 2 -- 1 file changed, 2 deletions(-) diff --git a/app-common/src/main/res/layout/euicc_info_item.xml b/app-common/src/main/res/layout/euicc_info_item.xml index 39d15a6..fa148fb 100644 --- a/app-common/src/main/res/layout/euicc_info_item.xml +++ b/app-common/src/main/res/layout/euicc_info_item.xml @@ -22,8 +22,6 @@ android:layout_height="wrap_content" android:layout_marginHorizontal="24dp" android:layout_marginVertical="12dp" - android:maxLines="1" - android:ellipsize="marquee" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toBottomOf="@id/euicc_info_title"