Compare commits

...

3 commits

Author SHA1 Message Date
790a5cf778 OpenEuiccService: handle errors in disabling active profiles
All checks were successful
/ build-debug (push) Successful in 5m6s
2024-06-16 20:58:07 -04:00
261ad6dbeb OpenEuiccService: track LPA actions for notifications 2024-06-16 20:53:25 -04:00
f73af48b59 refactor: beginTrackedOperation should be a LPA extension 2024-06-16 20:25:25 -04:00
4 changed files with 70 additions and 47 deletions

View file

@ -1,15 +1,10 @@
package im.angry.openeuicc.util package im.angry.openeuicc.util
import android.os.Bundle import android.os.Bundle
import android.util.Log
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import im.angry.openeuicc.core.EuiccChannel import im.angry.openeuicc.core.EuiccChannel
import im.angry.openeuicc.core.EuiccChannelManager import im.angry.openeuicc.core.EuiccChannelManager
import im.angry.openeuicc.ui.BaseEuiccAccessActivity import im.angry.openeuicc.ui.BaseEuiccAccessActivity
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
private const val TAG = "EuiccChannelFragmentUtils"
interface EuiccChannelFragmentMarker: OpenEuiccContextMarker interface EuiccChannelFragmentMarker: OpenEuiccContextMarker
@ -37,32 +32,9 @@ val <T> T.channel: EuiccChannel where T: Fragment, T: EuiccChannelFragmentMarker
get() = get() =
euiccChannelManager.findEuiccChannelByPortBlocking(slotId, portId)!! 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> 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 { interface EuiccProfilesChangedListener {
fun onEuiccProfilesChanged() fun onEuiccProfilesChanged()
} }
suspend fun <T> T.beginTrackedOperation(op: suspend () -> Boolean) where T: Fragment, T: EuiccChannelFragmentMarker =
channel.lpa.beginTrackedOperation(op)

View file

@ -2,9 +2,14 @@ package im.angry.openeuicc.util
import android.util.Log import android.util.Log
import im.angry.openeuicc.core.EuiccChannel 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.LocalProfileAssistant
import net.typeblog.lpac_jni.LocalProfileInfo import net.typeblog.lpac_jni.LocalProfileInfo
const val TAG = "LPAUtils"
val LocalProfileInfo.displayName: String val LocalProfileInfo.displayName: String
get() = nickName.ifEmpty { name } get() = nickName.ifEmpty { name }
@ -26,7 +31,7 @@ val List<EuiccChannel>.hasMultipleChips: Boolean
*/ */
fun LocalProfileAssistant.disableActiveProfile(refresh: Boolean): Boolean = fun LocalProfileAssistant.disableActiveProfile(refresh: Boolean): Boolean =
profiles.find { it.isEnabled }?.let { 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) disableProfile(it.iccid, refresh)
} ?: true } ?: true
@ -41,3 +46,33 @@ fun LocalProfileAssistant.disableActiveProfileWithUndo(refreshOnDisable: Boolean
disableProfile(it.iccid, refreshOnDisable) disableProfile(it.iccid, refreshOnDisable)
return { enableProfile(it.iccid) } return { enableProfile(it.iccid) }
} ?: { } } ?: { }
/**
* 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")
}

View file

@ -12,6 +12,8 @@ import net.typeblog.lpac_jni.LocalProfileInfo
import im.angry.openeuicc.core.EuiccChannel import im.angry.openeuicc.core.EuiccChannel
import im.angry.openeuicc.core.EuiccChannelManager import im.angry.openeuicc.core.EuiccChannelManager
import im.angry.openeuicc.util.* import im.angry.openeuicc.util.*
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.single
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import java.lang.IllegalStateException import java.lang.IllegalStateException
@ -226,11 +228,17 @@ class OpenEuiccService : EuiccService(), OpenEuiccContextMarker {
} }
} }
return if (channels[0].lpa.deleteProfile(iccid)) { channels[0].lpa.beginTrackedOperationBlocking {
RESULT_OK if (channels[0].lpa.deleteProfile(iccid)) {
} else { return RESULT_OK
RESULT_FIRST_USER }
runBlocking {
preferenceRepository.notificationDeleteFlow.first()
}
} }
return RESULT_FIRST_USER
} catch (e: Exception) { } catch (e: Exception) {
return RESULT_FIRST_USER return RESULT_FIRST_USER
} }
@ -283,14 +291,22 @@ class OpenEuiccService : EuiccService(), OpenEuiccContextMarker {
return RESULT_FIRST_USER return RESULT_FIRST_USER
} }
// Disable any active profile first if present channel.lpa.beginTrackedOperationBlocking {
if (!channel.lpa.disableActiveProfile(false)) { if (iccid != null) {
return RESULT_FIRST_USER // 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) { runBlocking {
if (!channel.lpa.enableProfile(iccid)) { // TODO: The enable / disable operations should really be one
return RESULT_FIRST_USER preferenceRepository.notificationEnableFlow.first()
} }
} }

View file

@ -10,8 +10,8 @@ interface LocalProfileAssistant {
// All blocking functions in this class assume that they are executed on non-Main threads // 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. // The IO context in Kotlin's coroutine library is recommended.
fun enableProfile(iccid: String, refresh: Boolean = false): Boolean fun enableProfile(iccid: String, refresh: Boolean = true): Boolean
fun disableProfile(iccid: String, refresh: Boolean = false): Boolean fun disableProfile(iccid: String, refresh: Boolean = true): Boolean
fun deleteProfile(iccid: String): Boolean fun deleteProfile(iccid: String): Boolean
fun downloadProfile(smdp: String, matchingId: String?, imei: String?, fun downloadProfile(smdp: String, matchingId: String?, imei: String?,