Browse Source

Implement TelephonyManagerApduChannel

This does not work yet because only the LPA which implements
EuiccService can access the channel. Let's hope however everything we
did here *should* work.
Peter Cai 3 years ago
parent
commit
5894dc9a71

+ 28 - 4
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)

+ 90 - 0
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<ApduChannel, EuiccChannelStateManager>? {
+            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>): 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?) {
+    }
+}