Compare commits

...

2 commits

6 changed files with 166 additions and 18 deletions

View file

@ -5,9 +5,9 @@ import android.content.Context
import android.os.Handler import android.os.Handler
import android.os.HandlerThread import android.os.HandlerThread
import android.se.omapi.SEService import android.se.omapi.SEService
import android.telephony.UiccCardInfo
import android.util.Log import android.util.Log
import im.angry.openeuicc.OpenEuiccApplication import im.angry.openeuicc.OpenEuiccApplication
import im.angry.openeuicc.util.*
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.Mutex
@ -52,27 +52,27 @@ open class EuiccChannelManager(protected val context: Context) {
} }
} }
protected open fun tryOpenEuiccChannelPrivileged(uiccInfo: UiccCardInfo, channelInfo: EuiccChannelInfo): EuiccChannel? { protected open fun tryOpenEuiccChannelPrivileged(uiccInfo: UiccCardInfoCompat, channelInfo: EuiccChannelInfo): EuiccChannel? {
// No-op when unprivileged // No-op when unprivileged
return null return null
} }
protected fun tryOpenEuiccChannelUnprivileged(uiccInfo: UiccCardInfo, channelInfo: EuiccChannelInfo): EuiccChannel? { protected fun tryOpenEuiccChannelUnprivileged(uiccInfo: UiccCardInfoCompat, channelInfo: EuiccChannelInfo): EuiccChannel? {
Log.i(TAG, "Trying OMAPI for slot ${uiccInfo.slotIndex}") Log.i(TAG, "Trying OMAPI for slot ${uiccInfo.physicalSlotIndex}")
try { try {
return OmapiChannel(seService!!, channelInfo) return OmapiChannel(seService!!, channelInfo)
} catch (e: IllegalArgumentException) { } catch (e: IllegalArgumentException) {
// Failed // Failed
Log.w(TAG, "OMAPI APDU interface unavailable for slot ${uiccInfo.slotIndex}.") Log.w(TAG, "OMAPI APDU interface unavailable for slot ${uiccInfo.physicalSlotIndex}.")
} }
return null return null
} }
private suspend fun tryOpenEuiccChannel(uiccInfo: UiccCardInfo): EuiccChannel? { private suspend fun tryOpenEuiccChannel(uiccInfo: UiccCardInfoCompat): EuiccChannel? {
lock.withLock { lock.withLock {
ensureSEService() ensureSEService()
val existing = channels.find { it.slotId == uiccInfo.slotIndex } val existing = channels.find { it.slotId == uiccInfo.physicalSlotIndex }
if (existing != null) { if (existing != null) {
if (existing.valid) { if (existing.valid) {
return existing return existing
@ -83,10 +83,10 @@ open class EuiccChannelManager(protected val context: Context) {
} }
val channelInfo = EuiccChannelInfo( val channelInfo = EuiccChannelInfo(
uiccInfo.slotIndex, uiccInfo.physicalSlotIndex,
uiccInfo.cardId, uiccInfo.cardId,
"SIM ${uiccInfo.slotIndex}", "SIM ${uiccInfo.physicalSlotIndex}",
tm.getImei(uiccInfo.slotIndex) ?: return null, tm.getImei(uiccInfo.physicalSlotIndex) ?: return null,
uiccInfo.isRemovable uiccInfo.isRemovable
) )
@ -105,7 +105,7 @@ open class EuiccChannelManager(protected val context: Context) {
} }
private suspend fun findEuiccChannelBySlot(slotId: Int): EuiccChannel? { private suspend fun findEuiccChannelBySlot(slotId: Int): EuiccChannel? {
return tm.uiccCardsInfo.find { it.slotIndex == slotId }?.let { return tm.uiccCardsInfoCompat.find { it.physicalSlotIndex == slotId }?.let {
tryOpenEuiccChannel(it) tryOpenEuiccChannel(it)
} }
} }
@ -123,9 +123,9 @@ open class EuiccChannelManager(protected val context: Context) {
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
ensureSEService() ensureSEService()
for (uiccInfo in tm.uiccCardsInfo) { for (uiccInfo in tm.uiccCardsInfoCompat) {
if (tryOpenEuiccChannel(uiccInfo) != null) { if (tryOpenEuiccChannel(uiccInfo) != null) {
Log.d(TAG, "Found eUICC on slot ${uiccInfo.slotIndex}") Log.d(TAG, "Found eUICC on slot ${uiccInfo.physicalSlotIndex}")
} }
} }
} }

View file

@ -0,0 +1,75 @@
package im.angry.openeuicc.util
import android.annotation.SuppressLint
import android.os.Build
import android.telephony.TelephonyManager
import android.telephony.UiccCardInfo
import android.telephony.UiccPortInfo
import im.angry.openeuicc.util.*
import java.lang.RuntimeException
@Suppress("DEPRECATION")
class UiccCardInfoCompat(val inner: UiccCardInfo) {
val physicalSlotIndex: Int
get() =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
inner.physicalSlotIndex
} else {
inner.slotIndex
}
val ports: Collection<UiccPortInfoCompat>
get() =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
inner.ports.map { UiccPortInfoCompat(it, this) }
} else {
listOf(UiccPortInfoCompat(null, this))
}
val isEuicc: Boolean
get() = inner.isEuicc
val isRemovable: Boolean
get() = inner.isRemovable
val isMultipleEnabledProfilesSupported: Boolean
get() =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
inner.isMultipleEnabledProfilesSupported
} else {
false
}
val cardId: Int
get() = inner.cardId
}
class UiccPortInfoCompat(private val _inner: Any?, val card: UiccCardInfoCompat) {
init {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
check(_inner != null && _inner is UiccPortInfo) {
"_inner is not UiccPortInfo on TIRAMISU"
}
}
}
val inner: UiccPortInfo
get() =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
_inner as UiccPortInfo
} else {
throw RuntimeException("UiccPortInfo does not exist before T")
}
val portIndex: Int
get() =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
inner.portIndex
} else {
0
}
}
val TelephonyManager.uiccCardsInfoCompat: List<UiccCardInfoCompat>
@SuppressLint("MissingPermission")
get() = uiccCardsInfo.map { UiccCardInfoCompat(it) }

View file

@ -1,7 +1,6 @@
package im.angry.openeuicc.core package im.angry.openeuicc.core
import android.content.Context import android.content.Context
import android.telephony.UiccCardInfo
import android.util.Log import android.util.Log
import im.angry.openeuicc.OpenEuiccApplication import im.angry.openeuicc.OpenEuiccApplication
import im.angry.openeuicc.util.* import im.angry.openeuicc.util.*
@ -11,7 +10,7 @@ import java.lang.IllegalArgumentException
class PrivilegedEuiccChannelManager(context: Context): EuiccChannelManager(context) { class PrivilegedEuiccChannelManager(context: Context): EuiccChannelManager(context) {
override fun checkPrivileges() = true // TODO: Implement proper system app check override fun checkPrivileges() = true // TODO: Implement proper system app check
override fun tryOpenEuiccChannelPrivileged(uiccInfo: UiccCardInfo, channelInfo: EuiccChannelInfo): EuiccChannel? { override fun tryOpenEuiccChannelPrivileged(uiccInfo: UiccCardInfoCompat, channelInfo: EuiccChannelInfo): EuiccChannel? {
if (uiccInfo.isRemovable) { if (uiccInfo.isRemovable) {
// Attempt unprivileged (OMAPI) before TelephonyManager // Attempt unprivileged (OMAPI) before TelephonyManager
// but still try TelephonyManager in case OMAPI is broken // but still try TelephonyManager in case OMAPI is broken
@ -19,13 +18,13 @@ class PrivilegedEuiccChannelManager(context: Context): EuiccChannelManager(conte
} }
if (uiccInfo.isEuicc) { if (uiccInfo.isEuicc) {
Log.i(TAG, "Trying TelephonyManager for slot ${uiccInfo.slotIndex}") Log.i(TAG, "Trying TelephonyManager for slot ${uiccInfo.physicalSlotIndex}")
// TODO: On Tiramisu, we should also connect all available "ports" for MEP support // TODO: On Tiramisu, we should also connect all available "ports" for MEP support
try { try {
return TelephonyManagerChannel(channelInfo, tm) return TelephonyManagerChannel(channelInfo, tm)
} catch (e: IllegalArgumentException) { } catch (e: IllegalArgumentException) {
// Failed // Failed
Log.w(TAG, "TelephonyManager APDU interface unavailable for slot ${uiccInfo.slotIndex}, falling back") Log.w(TAG, "TelephonyManager APDU interface unavailable for slot ${uiccInfo.physicalSlotIndex}, falling back")
} }
} }
return null return null

View file

@ -0,0 +1,39 @@
package im.angry.openeuicc.util
import android.os.Build
import android.telephony.IccOpenLogicalChannelResponse
import android.telephony.TelephonyManager
// TODO: Usage of *byPort APIs will still break build in-tree on lower AOSP versions
// Maybe older versions should simply include hidden-apis-shim when building?
fun TelephonyManager.iccOpenLogicalChannelByPortCompat(
slotIndex: Int, portIndex: Int, aid: String?, p2: Int
): IccOpenLogicalChannelResponse =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
iccOpenLogicalChannelByPort(slotIndex, portIndex, aid, p2)
} else {
iccOpenLogicalChannelBySlot(slotIndex, aid, p2)
}
fun TelephonyManager.iccCloseLogicalChannelByPortCompat(
slotIndex: Int, portIndex: Int, channel: Int
) =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
iccCloseLogicalChannelByPort(slotIndex, portIndex, channel)
} else {
iccCloseLogicalChannelBySlot(slotIndex, channel)
}
fun TelephonyManager.iccTransmitApduLogicalChannelByPortCompat(
slotIndex: Int, portIndex: Int, channel: Int,
cla: Int, inst: Int, p1: Int, p2: Int, p3: Int, data: String?
): String? =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
iccTransmitApduLogicalChannelByPort(
slotIndex, portIndex, channel, cla, inst, p1, p2, p3, data
)
} else {
iccTransmitApduLogicalChannelBySlot(
slotIndex, channel, cla, inst, p1, p2, p3, data
)
}

View file

@ -14,12 +14,24 @@ private val iccOpenLogicalChannelBySlot: Method by lazy {
Int::class.java, String::class.java, Int::class.java Int::class.java, String::class.java, Int::class.java
) )
} }
private val iccOpenLogicalChannelByPort: Method by lazy {
TelephonyManager::class.java.getMethod(
"iccOpenLogicalChannelByPort",
Int::class.java, Int::class.java, String::class.java, Int::class.java
)
}
private val iccCloseLogicalChannelBySlot: Method by lazy { private val iccCloseLogicalChannelBySlot: Method by lazy {
TelephonyManager::class.java.getMethod( TelephonyManager::class.java.getMethod(
"iccCloseLogicalChannelBySlot", "iccCloseLogicalChannelBySlot",
Int::class.java, Int::class.java Int::class.java, Int::class.java
) )
} }
private val iccCloseLogicalChannelByPort: Method by lazy {
TelephonyManager::class.java.getMethod(
"iccCloseLogicalChannelByPort",
Int::class.java, Int::class.java, Int::class.java
)
}
private val iccTransmitApduLogicalChannelBySlot: Method by lazy { private val iccTransmitApduLogicalChannelBySlot: Method by lazy {
TelephonyManager::class.java.getMethod( TelephonyManager::class.java.getMethod(
"iccTransmitApduLogicalChannelBySlot", "iccTransmitApduLogicalChannelBySlot",
@ -27,15 +39,30 @@ private val iccTransmitApduLogicalChannelBySlot: Method by lazy {
Int::class.java, Int::class.java, Int::class.java, String::class.java Int::class.java, Int::class.java, Int::class.java, String::class.java
) )
} }
private val iccTransmitApduLogicalChannelByPort: Method by lazy {
TelephonyManager::class.java.getMethod(
"iccTransmitApduLogicalChannelByPort",
Int::class.java, Int::class.java, Int::class.java, Int::class.java, Int::class.java,
Int::class.java, Int::class.java, Int::class.java, String::class.java
)
}
fun TelephonyManager.iccOpenLogicalChannelBySlot( fun TelephonyManager.iccOpenLogicalChannelBySlot(
slotId: Int, appletId: String, p2: Int slotId: Int, appletId: String?, p2: Int
): IccOpenLogicalChannelResponse = ): IccOpenLogicalChannelResponse =
iccOpenLogicalChannelBySlot.invoke(this, slotId, appletId, p2) as IccOpenLogicalChannelResponse iccOpenLogicalChannelBySlot.invoke(this, slotId, appletId, p2) as IccOpenLogicalChannelResponse
fun TelephonyManager.iccOpenLogicalChannelByPort(
slotId: Int, portId: Int, appletId: String?, p2: Int
): IccOpenLogicalChannelResponse =
iccOpenLogicalChannelByPort.invoke(this, slotId, portId, appletId, p2) as IccOpenLogicalChannelResponse
fun TelephonyManager.iccCloseLogicalChannelBySlot(slotId: Int, channel: Int): Boolean = fun TelephonyManager.iccCloseLogicalChannelBySlot(slotId: Int, channel: Int): Boolean =
iccCloseLogicalChannelBySlot.invoke(this, slotId, channel) as Boolean iccCloseLogicalChannelBySlot.invoke(this, slotId, channel) as Boolean
fun TelephonyManager.iccCloseLogicalChannelByPort(slotId: Int, portId: Int, channel: Int): Boolean =
iccCloseLogicalChannelByPort.invoke(this, slotId, portId, channel) as Boolean
fun TelephonyManager.iccTransmitApduLogicalChannelBySlot( fun TelephonyManager.iccTransmitApduLogicalChannelBySlot(
slotId: Int, channel: Int, cla: Int, instruction: Int, slotId: Int, channel: Int, cla: Int, instruction: Int,
p1: Int, p2: Int, p3: Int, data: String? p1: Int, p2: Int, p3: Int, data: String?
@ -44,6 +71,14 @@ fun TelephonyManager.iccTransmitApduLogicalChannelBySlot(
this, slotId, channel, cla, instruction, p1, p2, p3, data this, slotId, channel, cla, instruction, p1, p2, p3, data
) as String? ) as String?
fun TelephonyManager.iccTransmitApduLogicalChannelByPort(
slotId: Int, portId: Int, channel: Int, cla: Int, instruction: Int,
p1: Int, p2: Int, p3: Int, data: String?
): String? =
iccTransmitApduLogicalChannelByPort.invoke(
this, slotId, portId, channel, cla, instruction, p1, p2, p3, data
) as String?
private val requestEmbeddedSubscriptionInfoListRefresh: Method by lazy { private val requestEmbeddedSubscriptionInfoListRefresh: Method by lazy {
SubscriptionManager::class.java.getMethod("requestEmbeddedSubscriptionInfoListRefresh", Int::class.java) SubscriptionManager::class.java.getMethod("requestEmbeddedSubscriptionInfoListRefresh", Int::class.java)
} }