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
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> 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> 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()
}
}
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 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<EuiccChannel>.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) }
} ?: { }
} ?: { }
/**
* 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.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()
}
}

View file

@ -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?,