Compare commits

...

4 commits

Author SHA1 Message Date
Peter Cai 100d2e2866 [12/n] OpenEuiccService: Improve compatibility with AOSP Settings UI 2023-12-17 14:38:01 -05:00
Peter Cai 470fe5c545 OpenEuiccService: more logging 2023-12-17 13:01:14 -05:00
Peter Cai d95341b764 [11/n] lpac-jni: Fix validity check for LPA
none of the properties should be "by lazy" because we depend on whether
they error to detect if the LPA is still valid.
2023-12-17 11:18:45 -05:00
Peter Cai 5948499896 [10/n] OpenEuiccService: Implemennt onSwitchToSubscriptionWithPort 2023-12-17 10:59:24 -05:00
3 changed files with 120 additions and 28 deletions

View file

@ -121,6 +121,20 @@ open class EuiccChannelManager(protected val context: Context) {
}
}
fun findEuiccChannelByPhysicalSlotBlocking(physicalSlotId: Int): EuiccChannel? = runBlocking {
if (!checkPrivileges()) return@runBlocking null
withContext(Dispatchers.IO) {
for (card in tm.uiccCardsInfoCompat) {
if (card.physicalSlotIndex != physicalSlotId) continue
for (port in card.ports) {
tryOpenEuiccChannel(port)?.let { return@withContext it }
}
}
null
}
}
fun findEuiccChannelByPortBlocking(physicalSlotId: Int, portId: Int): EuiccChannel? = runBlocking {
if (!checkPrivileges()) return@runBlocking null
withContext(Dispatchers.IO) {

View file

@ -1,20 +1,31 @@
package im.angry.openeuicc.service
import android.service.euicc.*
import android.telephony.UiccSlotMapping
import android.telephony.euicc.DownloadableSubscription
import android.telephony.euicc.EuiccInfo
import android.util.Log
import net.typeblog.lpac_jni.LocalProfileInfo
import im.angry.openeuicc.OpenEuiccApplication
import im.angry.openeuicc.core.EuiccChannel
import im.angry.openeuicc.util.*
import java.lang.IllegalStateException
class OpenEuiccService : EuiccService() {
companion object {
const val TAG = "OpenEuiccService"
}
private val openEuiccApplication
get() = application as OpenEuiccApplication
private fun findChannel(slotId: Int): EuiccChannel? =
private fun findChannel(physicalSlotId: Int): EuiccChannel? =
openEuiccApplication.euiccChannelManager
.findEuiccChannelBySlotBlocking(slotId)
.findEuiccChannelByPhysicalSlotBlocking(physicalSlotId)
private fun findChannel(slotId: Int, portId: Int): EuiccChannel? =
openEuiccApplication.euiccChannelManager
.findEuiccChannelByPortBlocking(slotId, portId)
override fun onGetEid(slotId: Int): String? =
findChannel(slotId)?.lpa?.eID
@ -25,6 +36,47 @@ class OpenEuiccService : EuiccService() {
private fun EuiccChannel.profileExists(iccid: String?) =
lpa.profiles.any { it.iccid == iccid }
private fun ensurePortIsMapped(slotId: Int, portId: Int) {
val mappings = openEuiccApplication.telephonyManager.simSlotMapping.toMutableList()
mappings.firstOrNull { it.physicalSlotIndex == slotId && it.portIndex == portId }?.let {
throw IllegalStateException("Slot $slotId port $portId has already been mapped")
}
val idx = mappings.indexOfFirst { it.physicalSlotIndex != slotId || it.portIndex != portId }
if (idx >= 0) {
mappings[idx] = UiccSlotMapping(portId, slotId, mappings[idx].logicalSlotIndex)
}
mappings.firstOrNull { it.physicalSlotIndex == slotId && it.portIndex == portId } ?: run {
throw IllegalStateException("Cannot map slot $slotId port $portId")
}
try {
openEuiccApplication.telephonyManager.simSlotMapping = mappings
return
} catch (_: Exception) {
}
// Sometimes hardware supports one ordering but not the reverse
openEuiccApplication.telephonyManager.simSlotMapping = mappings.reversed()
}
private fun <T> retryWithTimeout(timeoutMillis: Int, backoff: Int = 1000, f: () -> T?): T? {
val startTimeMillis = System.currentTimeMillis()
do {
try {
f()?.let { return@retryWithTimeout it }
} catch (_: Exception) {
// Ignore
} finally {
Thread.sleep(backoff.toLong())
}
} while (System.currentTimeMillis() - startTimeMillis < timeoutMillis)
return null
}
override fun onGetOtaStatus(slotId: Int): Int {
// Not implemented
return 5 // EUICC_OTA_STATUS_UNAVAILABLE
@ -56,6 +108,7 @@ class OpenEuiccService : EuiccService() {
}
override fun onGetEuiccProfileInfoList(slotId: Int): GetEuiccProfileInfoListResult? {
Log.i(TAG, "onGetEuiccProfileInfoList slotId=$slotId")
val channel = findChannel(slotId) ?: return null
val profiles = channel.lpa.profiles.operational.map {
EuiccProfileInfo.Builder(it.iccid).apply {
@ -86,6 +139,7 @@ class OpenEuiccService : EuiccService() {
}
override fun onDeleteSubscription(slotId: Int, iccid: String): Int {
Log.i(TAG, "onDeleteSubscription slotId=$slotId iccid=$iccid")
try {
val channel = findChannel(slotId) ?: return RESULT_FIRST_USER
@ -99,6 +153,7 @@ class OpenEuiccService : EuiccService() {
if (profile.state == LocalProfileInfo.State.Enabled) {
// Must disable the profile first
// TODO: Need to check "other port" as well for MEP
return RESULT_FIRST_USER
}
@ -112,40 +167,63 @@ class OpenEuiccService : EuiccService() {
}
}
// TODO: on some devices we need to update the mapping (and potentially disable a pSIM)
// for eSIM to be usable, in which case we will have to respect forceDeactivateSim.
// This is the same for our custom LUI. Both have to take this into consideration.
@Deprecated("Deprecated in Java")
override fun onSwitchToSubscription(
slotId: Int,
iccid: String?,
forceDeactivateSim: Boolean
): Int {
try {
val channel = findChannel(slotId) ?: return RESULT_FIRST_USER
): Int =
// -1 = any port
onSwitchToSubscriptionWithPort(slotId, -1, iccid, forceDeactivateSim)
if (!channel.profileExists(iccid)) {
override fun onSwitchToSubscriptionWithPort(
slotId: Int,
portIndex: Int,
iccid: String?,
forceDeactivateSim: Boolean
): Int {
Log.i(TAG,"onSwitchToSubscriptionWithPort slotId=$slotId portIndex=$portIndex iccid=$iccid forceDeactivateSim=$forceDeactivateSim")
try {
// retryWithTimeout is needed here because this function may be called just after
// AOSP has switched slot mappings, in which case the slots may not be ready yet.
val channel = if (portIndex == -1) {
retryWithTimeout(5000) { findChannel(slotId) }
} else {
retryWithTimeout(5000) { findChannel(slotId, portIndex) }
} ?: run {
if (!forceDeactivateSim) {
// The user must select which SIM to deactivate
return@onSwitchToSubscriptionWithPort RESULT_MUST_DEACTIVATE_SIM
} else {
try {
// If we are allowed to deactivate any SIM we like, try mapping the indicated port first
ensurePortIsMapped(slotId, portIndex)
retryWithTimeout(5000) { findChannel(slotId, portIndex) }
} catch (e: Exception) {
// We cannot map the port (or it is already mapped)
// but we can also use any port available on the card
retryWithTimeout(5000) { findChannel(slotId) }
} ?: return@onSwitchToSubscriptionWithPort RESULT_FIRST_USER
}
}
if (iccid != null && !channel.profileExists(iccid)) {
Log.i(TAG, "onSwitchToSubscriptionWithPort iccid=$iccid not found")
return RESULT_FIRST_USER
}
if (iccid == null) {
// Disable active profile
val activeProfile = channel.lpa.profiles.find {
it.state == LocalProfileInfo.State.Enabled
} ?: return RESULT_OK
// Disable any active profile first if present
channel.lpa.profiles.find {
it.state == LocalProfileInfo.State.Enabled
}?.let { if (!channel.lpa.disableProfile(it.iccid)) return RESULT_FIRST_USER }
return if (channel.lpa.disableProfile(activeProfile.iccid)) {
RESULT_OK
} else {
RESULT_FIRST_USER
}
} else {
return if (channel.lpa.enableProfile(iccid)) {
RESULT_OK
} else {
RESULT_FIRST_USER
if (iccid != null) {
if (!channel.lpa.enableProfile(iccid)) {
return RESULT_FIRST_USER
}
}
return RESULT_OK
} catch (e: Exception) {
return RESULT_FIRST_USER
} finally {
@ -154,6 +232,7 @@ class OpenEuiccService : EuiccService() {
}
override fun onUpdateSubscriptionNickname(slotId: Int, iccid: String, nickname: String?): Int {
Log.i(TAG, "onUpdateSubscriptionNickname slotId=$slotId iccid=$iccid nickname=$nickname")
val channel = findChannel(slotId) ?: return RESULT_FIRST_USER
if (!channel.profileExists(iccid)) {
return RESULT_FIRST_USER

View file

@ -20,11 +20,10 @@ class LocalProfileAssistantImpl(
}
override val profiles: List<LocalProfileInfo>
get() = LpacJni.es10cGetProfilesInfo(contextHandle)!!.asList() // TODO: Maybe we need better error handling
get() = LpacJni.es10cGetProfilesInfo(contextHandle)!!.asList()
override val eID: String by lazy {
LpacJni.es10cGetEid(contextHandle)!!
}
override val eID: String
get() = LpacJni.es10cGetEid(contextHandle)!!
override val euiccInfo2: EuiccInfo2?
get() = LpacJni.es10cexGetEuiccInfo2(contextHandle)