From 451b17d0a58a9a19687e0d8da79b23e66665069d Mon Sep 17 00:00:00 2001 From: Peter Cai Date: Mon, 1 Jan 2024 20:02:46 -0500 Subject: [PATCH] refactor: Stop exitting the application when enabling/disabling profiles ..instead, attempt to reconnect the LPA. This is needed to properly send notifications after the operation. --- .../openeuicc/ui/EuiccManagementFragment.kt | 16 ++--- app-common/src/main/res/values/strings.xml | 1 - .../core/TelephonyManagerApduInterface.kt | 1 + .../lpac_jni/LocalProfileAssistant.kt | 4 +- .../impl/LocalProfileAssistantImpl.kt | 65 +++++++++++++++++-- 5 files changed, 67 insertions(+), 20 deletions(-) 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 bce83bf..d6ffa32 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 @@ -127,7 +127,6 @@ open class EuiccManagementFragment : Fragment(), EuiccFragmentMarker, EuiccProfi private fun enableOrDisableProfile(iccid: String, enable: Boolean) { swipeRefresh.isRefreshing = true - swipeRefresh.isEnabled = false fab.isEnabled = false lifecycleScope.launch { @@ -137,15 +136,12 @@ open class EuiccManagementFragment : Fragment(), EuiccFragmentMarker, EuiccProfi } else { doDisableProfile(iccid) } - Toast.makeText(context, R.string.toast_profile_enabled, Toast.LENGTH_LONG).show() - // The APDU channel will be invalid when the SIM reboots. For now, just exit the app - euiccChannelManager.invalidate() - requireActivity().finish() + refresh() + fab.isEnabled = true } catch (e: Exception) { Log.d(TAG, "Failed to enable / disable profile $iccid") Log.d(TAG, Log.getStackTraceString(e)) fab.isEnabled = true - swipeRefresh.isEnabled = true Toast.makeText(context, R.string.toast_profile_enable_failed, Toast.LENGTH_LONG).show() } } @@ -153,14 +149,14 @@ open class EuiccManagementFragment : Fragment(), EuiccFragmentMarker, EuiccProfi private suspend fun doEnableProfile(iccid: String) = channel.lpa.beginOperation { - channel.lpa.enableProfile(iccid) - preferenceRepository.notificationEnableFlow.first() + channel.lpa.enableProfile(iccid, reconnectTimeout = 15 * 1000) && + preferenceRepository.notificationEnableFlow.first() } private suspend fun doDisableProfile(iccid: String) = channel.lpa.beginOperation { - channel.lpa.disableProfile(iccid) - preferenceRepository.notificationDisableFlow.first() + channel.lpa.disableProfile(iccid, reconnectTimeout = 15 * 1000) && + preferenceRepository.notificationDisableFlow.first() } sealed class ViewHolder(root: View) : RecyclerView.ViewHolder(root) { diff --git a/app-common/src/main/res/values/strings.xml b/app-common/src/main/res/values/strings.xml index 02d337d..1767c08 100644 --- a/app-common/src/main/res/values/strings.xml +++ b/app-common/src/main/res/values/strings.xml @@ -16,7 +16,6 @@ Delete Rename - eSIM profile switched. Please wait for a while when the card is restarting. Cannot switch to new eSIM profile. Nickname cannot be longer than 64 characters 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 f4dc483..c0936a4 100644 --- a/app/src/main/java/im/angry/openeuicc/core/TelephonyManagerApduInterface.kt +++ b/app/src/main/java/im/angry/openeuicc/core/TelephonyManagerApduInterface.kt @@ -20,6 +20,7 @@ class TelephonyManagerApduInterface( override fun disconnect() { // Do nothing + lastChannel = -1 } override fun logicalChannelOpen(aid: ByteArray): Int { 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 cecafd6..3548106 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 @@ -17,8 +17,8 @@ interface LocalProfileAssistant { // All blocking functions in this class assume that they are executed on non-Main threads // The IO context in Kotlin's coroutine library is recommended. - fun enableProfile(iccid: String): Boolean - fun disableProfile(iccid: String): Boolean + fun enableProfile(iccid: String, reconnectTimeout: Long = 0): Boolean + fun disableProfile(iccid: String, reconnectTimeout: Long = 0): Boolean fun deleteProfile(iccid: String): Boolean fun downloadProfile(smdp: String, matchingId: String?, imei: 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 fa82459..0a0bde1 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 @@ -1,6 +1,9 @@ package net.typeblog.lpac_jni.impl import android.util.Log +import kotlinx.coroutines.delay +import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.withTimeout import net.typeblog.lpac_jni.LpacJni import net.typeblog.lpac_jni.ApduInterface import net.typeblog.lpac_jni.EuiccInfo2 @@ -11,20 +14,52 @@ import net.typeblog.lpac_jni.LocalProfileNotification import net.typeblog.lpac_jni.ProfileDownloadCallback class LocalProfileAssistantImpl( - apduInterface: ApduInterface, - httpInterface: HttpInterface + private val apduInterface: ApduInterface, + private val httpInterface: HttpInterface ): LocalProfileAssistant { companion object { private const val TAG = "LocalProfileAssistantImpl" } - private val contextHandle: Long = LpacJni.createContext(apduInterface, httpInterface) + private var contextHandle: Long = LpacJni.createContext(apduInterface, httpInterface) init { if (LpacJni.es10xInit(contextHandle) < 0) { throw IllegalArgumentException("Failed to initialize LPA") } } + private fun tryReconnect(timeoutMillis: Long) = runBlocking { + withTimeout(timeoutMillis) { + try { + LpacJni.es10xFini(contextHandle) + LpacJni.destroyContext(contextHandle) + } catch (e: Exception) { + // Ignored + } + + while (true) { + try { + apduInterface.disconnect() + } catch (e: Exception) { + // Ignored + } + + try { + apduInterface.connect() + contextHandle = LpacJni.createContext(apduInterface, httpInterface) + val res = LpacJni.es10xInit(contextHandle) + Log.d(TAG, "$res") + check(res >= 0) { "Reconnect attempt failed" } + break + } catch (e: Exception) { + e.printStackTrace() + // continue retrying + delay(1000) + } + } + } + } + override val profiles: List get() = LpacJni.es10cGetProfilesInfo(contextHandle)!!.asList() @@ -39,12 +74,28 @@ class LocalProfileAssistantImpl( override val euiccInfo2: EuiccInfo2? get() = LpacJni.es10cexGetEuiccInfo2(contextHandle) - override fun enableProfile(iccid: String): Boolean { - return LpacJni.es10cEnableProfile(contextHandle, iccid) == 0 + override fun enableProfile(iccid: String, reconnectTimeout: Long): Boolean { + val res = LpacJni.es10cEnableProfile(contextHandle, iccid) == 0 + if (reconnectTimeout > 0) { + try { + tryReconnect(reconnectTimeout) + } catch (e: Exception) { + return false + } + } + return res } - override fun disableProfile(iccid: String): Boolean { - return LpacJni.es10cDisableProfile(contextHandle, iccid) == 0 + override fun disableProfile(iccid: String, reconnectTimeout: Long): Boolean { + val res = LpacJni.es10cDisableProfile(contextHandle, iccid) == 0 + if (reconnectTimeout > 0) { + try { + tryReconnect(reconnectTimeout) + } catch (e: Exception) { + return false + } + } + return res } override fun deleteProfile(iccid: String): Boolean {