package im.angry.openeuicc.util import android.util.Log import im.angry.openeuicc.core.EuiccChannel import im.angry.openeuicc.core.EuiccChannelManager import net.typeblog.lpac_jni.LocalProfileAssistant import net.typeblog.lpac_jni.LocalProfileInfo const val TAG = "LPAUtils" val LocalProfileInfo.displayName: String get() = nickName.ifEmpty { name } val LocalProfileInfo.isEnabled: Boolean get() = state == LocalProfileInfo.State.Enabled val List.operational: List get() = filter { it.profileClass == LocalProfileInfo.Clazz.Operational } val List.enabled: LocalProfileInfo? get() = find { it.isEnabled } val List.hasMultipleChips: Boolean get() = distinctBy { it.slotId }.size > 1 fun LocalProfileAssistant.switchProfile( iccid: String, enable: Boolean = false, refresh: Boolean = false ): Boolean = if (enable) { enableProfile(iccid, refresh) } else { disableProfile(iccid, refresh) } /** * Disable the current active profile if any. If refresh is true, also cause a refresh command. * See EuiccManager.waitForReconnect() */ fun LocalProfileAssistant.disableActiveProfile(refresh: Boolean): Boolean = profiles.enabled?.let { Log.i(TAG, "Disabling active profile ${it.iccid}") disableProfile(it.iccid, refresh) } ?: true /** * Disable the current active profile if any. If refresh is true, also cause a refresh command. * See EuiccManager.waitForReconnect() * * Return the iccid of the profile being disabled, or null if no active profile found or failed to * disable. */ fun LocalProfileAssistant.disableActiveProfileKeepIccId(refresh: Boolean): String? = profiles.enabled?.let { Log.i(TAG, "Disabling active profile ${it.iccid}") if (disableProfile(it.iccid, refresh)) { it.iccid } else { null } } /** * 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. * * This requires the EuiccChannelManager object and a slotId / portId instead of * just an LPA object, because a LPA might become invalid during an operation * that generates notifications. As such, we will end up having to reconnect * when this happens. * * Note that however, if reconnect is required and will not be instant, waiting * should be the concern of op() itself, and this function assumes that when * op() returns, the slotId and portId will correspond to a valid channel again. */ suspend inline fun EuiccChannelManager.beginTrackedOperation( slotId: Int, portId: Int, op: () -> Boolean ) { val latestSeq = withEuiccChannel(slotId, portId) { channel -> 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; // this is why we need to use two distinct calls to withEuiccChannel() withEuiccChannel(slotId, portId) { 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") }