diff --git a/app-common/src/main/java/im/angry/openeuicc/util/EuiccChannelFragmentUtils.kt b/app-common/src/main/java/im/angry/openeuicc/util/EuiccChannelFragmentUtils.kt index aff4154..582f86c 100644 --- a/app-common/src/main/java/im/angry/openeuicc/util/EuiccChannelFragmentUtils.kt +++ b/app-common/src/main/java/im/angry/openeuicc/util/EuiccChannelFragmentUtils.kt @@ -1,15 +1,10 @@ package im.angry.openeuicc.util import android.os.Bundle -import android.util.Log import androidx.fragment.app.Fragment import im.angry.openeuicc.core.EuiccChannel import im.angry.openeuicc.core.EuiccChannelManager import im.angry.openeuicc.ui.BaseEuiccAccessActivity -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.withContext - -private const val TAG = "EuiccChannelFragmentUtils" interface EuiccChannelFragmentMarker: OpenEuiccContextMarker @@ -37,32 +32,9 @@ val T.channel: EuiccChannel where T: Fragment, T: EuiccChannelFragmentMarker get() = euiccChannelManager.findEuiccChannelByPortBlocking(slotId, portId)!! -/* - * Begin a "tracked" operation where notifications may be generated by the eSIM - * Automatically handle any newly generated notification during the operation - * if the function "op" returns true. - */ -suspend fun T.beginTrackedOperation(op: suspend () -> Boolean) where T : Fragment, T : EuiccChannelFragmentMarker = - withContext(Dispatchers.IO) { - val latestSeq = channel.lpa.notifications.firstOrNull()?.seqNumber ?: 0 - Log.d(TAG, "Latest notification is $latestSeq before operation") - if (op()) { - Log.d(TAG, "Operation has requested notification handling") - try { - // Note that the exact instance of "channel" might have changed here if reconnected; - // so we MUST use the automatic getter for "channel" - channel.lpa.notifications.filter { it.seqNumber > latestSeq }.forEach { - Log.d(TAG, "Handling notification $it") - channel.lpa.handleNotification(it.seqNumber) - } - } catch (e: Exception) { - // Ignore any error during notification handling - e.printStackTrace() - } - } - Log.d(TAG, "Operation complete") - } - interface EuiccProfilesChangedListener { fun onEuiccProfilesChanged() -} \ No newline at end of file +} + +suspend fun T.beginTrackedOperation(op: suspend () -> Boolean) where T: Fragment, T: EuiccChannelFragmentMarker = + channel.lpa.beginTrackedOperation(op) \ No newline at end of file diff --git a/app-common/src/main/java/im/angry/openeuicc/util/LPAUtils.kt b/app-common/src/main/java/im/angry/openeuicc/util/LPAUtils.kt index 556320f..5d0c6ce 100644 --- a/app-common/src/main/java/im/angry/openeuicc/util/LPAUtils.kt +++ b/app-common/src/main/java/im/angry/openeuicc/util/LPAUtils.kt @@ -2,9 +2,14 @@ package im.angry.openeuicc.util import android.util.Log import im.angry.openeuicc.core.EuiccChannel +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.withContext import net.typeblog.lpac_jni.LocalProfileAssistant import net.typeblog.lpac_jni.LocalProfileInfo +const val TAG = "LPAUtils" + val LocalProfileInfo.displayName: String get() = nickName.ifEmpty { name } @@ -26,7 +31,7 @@ val List.hasMultipleChips: Boolean */ fun LocalProfileAssistant.disableActiveProfile(refresh: Boolean): Boolean = profiles.find { it.isEnabled }?.let { - Log.i("LPAUtils", "Disabling active profile ${it.iccid}") + Log.i(TAG, "Disabling active profile ${it.iccid}") disableProfile(it.iccid, refresh) } ?: true @@ -40,4 +45,34 @@ fun LocalProfileAssistant.disableActiveProfileWithUndo(refreshOnDisable: Boolean profiles.find { it.isEnabled }?.let { disableProfile(it.iccid, refreshOnDisable) return { enableProfile(it.iccid) } - } ?: { } \ No newline at end of file + } ?: { } + +/** + * Begin a "tracked" operation where notifications may be generated by the eSIM + * Automatically handle any newly generated notification during the operation + * if the function "op" returns true. + */ +suspend fun LocalProfileAssistant.beginTrackedOperation(op: suspend () -> Boolean) = + withContext(Dispatchers.IO) { + beginTrackedOperationBlocking { op() } + } + +inline fun LocalProfileAssistant.beginTrackedOperationBlocking(op: () -> Boolean) { + val latestSeq = notifications.firstOrNull()?.seqNumber ?: 0 + Log.d(TAG, "Latest notification is $latestSeq before operation") + if (op()) { + Log.d(TAG, "Operation has requested notification handling") + try { + // Note that the exact instance of "channel" might have changed here if reconnected; + // so we MUST use the automatic getter for "channel" + notifications.filter { it.seqNumber > latestSeq }.forEach { + Log.d(TAG, "Handling notification $it") + handleNotification(it.seqNumber) + } + } catch (e: Exception) { + // Ignore any error during notification handling + e.printStackTrace() + } + } + Log.d(TAG, "Operation complete") +} \ No newline at end of file diff --git a/app/src/main/java/im/angry/openeuicc/service/OpenEuiccService.kt b/app/src/main/java/im/angry/openeuicc/service/OpenEuiccService.kt index 74d2d75..4b46164 100644 --- a/app/src/main/java/im/angry/openeuicc/service/OpenEuiccService.kt +++ b/app/src/main/java/im/angry/openeuicc/service/OpenEuiccService.kt @@ -12,6 +12,8 @@ import net.typeblog.lpac_jni.LocalProfileInfo 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.flow.single import kotlinx.coroutines.runBlocking import java.lang.IllegalStateException @@ -226,11 +228,17 @@ class OpenEuiccService : EuiccService(), OpenEuiccContextMarker { } } - return if (channels[0].lpa.deleteProfile(iccid)) { - RESULT_OK - } else { - RESULT_FIRST_USER + channels[0].lpa.beginTrackedOperationBlocking { + if (channels[0].lpa.deleteProfile(iccid)) { + return RESULT_OK + } + + runBlocking { + preferenceRepository.notificationDeleteFlow.first() + } } + + return RESULT_FIRST_USER } catch (e: Exception) { return RESULT_FIRST_USER } @@ -283,14 +291,22 @@ class OpenEuiccService : EuiccService(), OpenEuiccContextMarker { return RESULT_FIRST_USER } - // Disable any active profile first if present - if (!channel.lpa.disableActiveProfile(false)) { - return RESULT_FIRST_USER - } + channel.lpa.beginTrackedOperationBlocking { + if (iccid != null) { + // Disable any active profile first if present + channel.lpa.disableActiveProfile(false) + if (!channel.lpa.enableProfile(iccid)) { + return RESULT_FIRST_USER + } + } else { + if (!channel.lpa.disableActiveProfile(true)) { + return RESULT_FIRST_USER + } + } - if (iccid != null) { - if (!channel.lpa.enableProfile(iccid)) { - return RESULT_FIRST_USER + runBlocking { + // TODO: The enable / disable operations should really be one + preferenceRepository.notificationEnableFlow.first() } } 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 89df38d..7e0a31b 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 @@ -10,8 +10,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, refresh: Boolean = false): Boolean - fun disableProfile(iccid: String, refresh: Boolean = false): Boolean + fun enableProfile(iccid: String, refresh: Boolean = true): Boolean + fun disableProfile(iccid: String, refresh: Boolean = true): Boolean fun deleteProfile(iccid: String): Boolean fun downloadProfile(smdp: String, matchingId: String?, imei: String?,