diff --git a/app/src/main/java/im/angry/openeuicc/core/EuiccChannelManager.kt b/app/src/main/java/im/angry/openeuicc/core/EuiccChannelManager.kt index 196c54f..2dd50f2 100644 --- a/app/src/main/java/im/angry/openeuicc/core/EuiccChannelManager.kt +++ b/app/src/main/java/im/angry/openeuicc/core/EuiccChannelManager.kt @@ -4,9 +4,11 @@ import android.content.Context import android.os.Handler import android.os.HandlerThread import android.se.omapi.SEService +import android.telephony.TelephonyManager import android.util.Log import com.truphone.lpa.ApduChannel import com.truphone.lpa.impl.LocalProfileAssistantImpl +import im.angry.openeuicc.OpenEuiccApplication import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.runBlocking import kotlinx.coroutines.withContext @@ -23,6 +25,10 @@ class EuiccChannelManager(private val context: Context) { private var seService: SEService? = null + private val tm by lazy { + (context.applicationContext as OpenEuiccApplication).telephonyManager + } + private val handler = Handler(HandlerThread("EuiccChannelManager").also { it.start() }.looper) private suspend fun connectSEService(): SEService = suspendCoroutine { cont -> @@ -50,12 +56,30 @@ class EuiccChannelManager(private val context: Context) { } } + val shouldTryTelephonyManager = + tm.uiccCardsInfo.find { it.slotIndex == slotId }?.let { + it.isEuicc && !it.isRemovable + } ?: false + var apduChannel: ApduChannel? = null var stateManager: EuiccChannelStateManager? = null - OmapiApduChannel.tryConnectUiccSlot(seService!!, slotId)?.let { (_apduChannel, _stateManager) -> - apduChannel = _apduChannel - stateManager = _stateManager - } ?: return null + + if (shouldTryTelephonyManager) { + Log.d(TAG, "Using TelephonyManager for slot $slotId") + // TODO: On Tiramisu, we should also connect all available "ports" for MEP support + TelephonyManagerApduChannel.tryConnectUiccSlot(tm, slotId)?.let { (_apduChannel, _stateManager) -> + apduChannel = _apduChannel + stateManager = _stateManager + } + } + + if (apduChannel == null || stateManager == null) { + OmapiApduChannel.tryConnectUiccSlot(seService!!, slotId) + ?.let { (_apduChannel, _stateManager) -> + apduChannel = _apduChannel + stateManager = _stateManager + } ?: return null + } val channel = EuiccChannel(slotId, "SIM $slotId", LocalProfileAssistantImpl(apduChannel), stateManager!!) channels.add(channel) diff --git a/app/src/main/java/im/angry/openeuicc/core/TelephonyManagerApduChannel.kt b/app/src/main/java/im/angry/openeuicc/core/TelephonyManagerApduChannel.kt new file mode 100644 index 0000000..3b4f217 --- /dev/null +++ b/app/src/main/java/im/angry/openeuicc/core/TelephonyManagerApduChannel.kt @@ -0,0 +1,90 @@ +package im.angry.openeuicc.core + +import android.telephony.IccOpenLogicalChannelResponse +import android.telephony.IccOpenLogicalChannelResponse.INVALID_CHANNEL +import android.telephony.IccOpenLogicalChannelResponse.STATUS_NO_ERROR +import android.telephony.TelephonyManager +import android.util.Log +import com.truphone.lpa.ApduChannel +import com.truphone.lpa.ApduTransmittedListener +import java.lang.Exception +import java.lang.reflect.Method + +class TelephonyManagerApduChannel( + private val tm: TelephonyManager, + private val slotId: Int, + private val channelId: Int) : ApduChannel { + + companion object { + private const val TAG = "TelephonyManagerApduChannel" + private const val EUICC_APP_ID = "A0000005591010FFFFFFFF8900000100" + + private val iccOpenLogicalChannelBySlot: Method = + TelephonyManager::class.java.getMethod("iccOpenLogicalChannelBySlot", + Int::class.java, String::class.java, Int::class.java) + private val iccCloseLogicalChannelBySlot: Method = + TelephonyManager::class.java.getMethod("iccCloseLogicalChannelBySlot", + Int::class.java, Int::class.java) + private val iccTransmitApduLogicalChannelBySlot: Method = + TelephonyManager::class.java.getMethod("iccTransmitApduLogicalChannelBySlot", + Int::class.java, Int::class.java, Int::class.java, Int::class.java, + Int::class.java, Int::class.java, Int::class.java, String::class.java) + + // TODO: On Tiramisu, we need to specify the portId also if we want MEP support + fun tryConnectUiccSlot(tm: TelephonyManager, slotId: Int): Pair? { + try { + val channel = iccOpenLogicalChannelBySlot.invoke(tm, slotId, EUICC_APP_ID, 0) as IccOpenLogicalChannelResponse + if (channel.status != STATUS_NO_ERROR || channel.channel == INVALID_CHANNEL) { + Log.e(TAG, "Unable to open eUICC channel for slot ${slotId} via TelephonyManager") + return null + } + + val stateManager = object : EuiccChannelStateManager { + override val valid: Boolean + get() = true // TODO: Fix this properly + + override fun destroy() { + iccCloseLogicalChannelBySlot.invoke(tm, slotId, channel.channel) + } + + } + + return Pair(TelephonyManagerApduChannel(tm, slotId, channel.channel), stateManager) + } catch (e: Exception) { + Log.e(TAG, "Unable to open eUICC channel for slot ${slotId} via TelephonyManager") + Log.e(TAG, Log.getStackTraceString(e)) + return null + } + } + } + + override fun transmitAPDU(apdu: String): String { + val cla = Integer.parseInt(apdu.substring(0, 2), 16) + val instruction = Integer.parseInt(apdu.substring(2, 4), 16) + val p1 = Integer.parseInt(apdu.substring(4, 6), 16) + val p2 = Integer.parseInt(apdu.substring(6, 8), 16) + val p3 = Integer.parseInt(apdu.substring(8, 10), 16) + val p4 = apdu.substring(10) + + return iccTransmitApduLogicalChannelBySlot.invoke( + tm, slotId, channelId, + cla, instruction, p1, p2, p3, p4) as String + } + + override fun transmitAPDUS(apdus: MutableList): String { + var res = "" + for (pdu in apdus) { + res = transmitAPDU(pdu) + } + return res + } + + override fun sendStatus() { + } + + override fun setApduTransmittedListener(apduTransmittedListener: ApduTransmittedListener?) { + } + + override fun removeApduTransmittedListener(apduTransmittedListener: ApduTransmittedListener?) { + } +} \ No newline at end of file