瀏覽代碼

refactor: [2/n] Center EuiccChannel's around ports, not cards

Peter Cai 2 年之前
父節點
當前提交
4090418146

+ 8 - 15
app-common/src/main/java/im/angry/openeuicc/core/EuiccChannel.kt

@@ -1,24 +1,17 @@
 package im.angry.openeuicc.core
 
+import im.angry.openeuicc.util.*
 import net.typeblog.lpac_jni.LocalProfileAssistant
 
-// A custom type to avoid compatibility issues with UiccCardInfo / UiccPortInfo
-data class EuiccChannelInfo(
-    val slotId: Int,
-    val cardId: Int,
-    val name: String,
-    val imei: String,
-    val removable: Boolean
-)
-
 abstract class EuiccChannel(
-    info: EuiccChannelInfo
+    port: UiccPortInfoCompat
 ) {
-    val slotId = info.slotId
-    val cardId = info.cardId
-    val name = info.name
-    val imei = info.imei
-    val removable = info.removable
+    val slotId = port.card.physicalSlotIndex // PHYSICAL slot
+    val logicalSlotId = port.logicalSlotIndex
+    val portId = port.portIndex
+    val cardId = port.card.cardId
+    val name = "SLOT ${port.card.physicalSlotIndex}:${port.portIndex}"
+    val removable = port.card.isRemovable
 
     abstract val lpa: LocalProfileAssistant
     val valid: Boolean

+ 36 - 27
app-common/src/main/java/im/angry/openeuicc/core/EuiccChannelManager.kt

@@ -1,6 +1,5 @@
 package im.angry.openeuicc.core
 
-import android.annotation.SuppressLint
 import android.content.Context
 import android.os.Handler
 import android.os.HandlerThread
@@ -17,7 +16,6 @@ import java.lang.IllegalArgumentException
 import kotlin.coroutines.resume
 import kotlin.coroutines.suspendCoroutine
 
-@SuppressLint("MissingPermission") // We rely on ARA-based privileges, not READ_PRIVILEGED_PHONE_STATE
 open class EuiccChannelManager(protected val context: Context) {
     companion object {
         const val TAG = "EuiccChannelManager"
@@ -52,27 +50,32 @@ open class EuiccChannelManager(protected val context: Context) {
          }
     }
 
-    protected open fun tryOpenEuiccChannelPrivileged(uiccInfo: UiccCardInfoCompat, channelInfo: EuiccChannelInfo): EuiccChannel? {
+    protected open fun tryOpenEuiccChannelPrivileged(port: UiccPortInfoCompat): EuiccChannel? {
         // No-op when unprivileged
         return null
     }
 
-    protected fun tryOpenEuiccChannelUnprivileged(uiccInfo: UiccCardInfoCompat, channelInfo: EuiccChannelInfo): EuiccChannel? {
-        Log.i(TAG, "Trying OMAPI for slot ${uiccInfo.physicalSlotIndex}")
+    protected fun tryOpenEuiccChannelUnprivileged(port: UiccPortInfoCompat): EuiccChannel? {
+        if (port.portIndex != 0) {
+            Log.w(TAG, "OMAPI channel attempted on non-zero portId, ignoring")
+            return null
+        }
+
+        Log.i(TAG, "Trying OMAPI for physical slot ${port.card.physicalSlotIndex}")
         try {
-            return OmapiChannel(seService!!, channelInfo)
+            return OmapiChannel(seService!!, port)
         } catch (e: IllegalArgumentException) {
             // Failed
-            Log.w(TAG, "OMAPI APDU interface unavailable for slot ${uiccInfo.physicalSlotIndex}.")
+            Log.w(TAG, "OMAPI APDU interface unavailable for physical slot ${port.card.physicalSlotIndex}.")
         }
 
         return null
     }
 
-    private suspend fun tryOpenEuiccChannel(uiccInfo: UiccCardInfoCompat): EuiccChannel? {
+    private suspend fun tryOpenEuiccChannel(port: UiccPortInfoCompat): EuiccChannel? {
         lock.withLock {
             ensureSEService()
-            val existing = channels.find { it.slotId == uiccInfo.physicalSlotIndex }
+            val existing = channels.find { it.slotId == port.card.physicalSlotIndex && it.portId == port.portIndex }
             if (existing != null) {
                 if (existing.valid) {
                     return existing
@@ -82,18 +85,10 @@ open class EuiccChannelManager(protected val context: Context) {
                 }
             }
 
-            val channelInfo = EuiccChannelInfo(
-                uiccInfo.physicalSlotIndex,
-                uiccInfo.cardId,
-                "SIM ${uiccInfo.physicalSlotIndex}",
-                tm.getImei(uiccInfo.physicalSlotIndex) ?: return null,
-                uiccInfo.isRemovable
-            )
-
-            var euiccChannel: EuiccChannel? = tryOpenEuiccChannelPrivileged(uiccInfo, channelInfo)
+            var euiccChannel: EuiccChannel? = tryOpenEuiccChannelPrivileged(port)
 
             if (euiccChannel == null) {
-                euiccChannel = tryOpenEuiccChannelUnprivileged(uiccInfo, channelInfo)
+                euiccChannel = tryOpenEuiccChannelUnprivileged(port)
             }
 
             if (euiccChannel != null) {
@@ -104,16 +99,28 @@ open class EuiccChannelManager(protected val context: Context) {
         }
     }
 
-    private suspend fun findEuiccChannelBySlot(slotId: Int): EuiccChannel? {
-        return tm.uiccCardsInfoCompat.find { it.physicalSlotIndex == slotId }?.let {
-            tryOpenEuiccChannel(it)
+    fun findEuiccChannelBySlotBlocking(logicalSlotId: Int): EuiccChannel? =
+        runBlocking {
+            if (!checkPrivileges()) return@runBlocking null
+            withContext(Dispatchers.IO) {
+                for (card in tm.uiccCardsInfoCompat) {
+                    for (port in card.ports) {
+                        if (port.logicalSlotIndex == logicalSlotId) {
+                            return@withContext tryOpenEuiccChannel(port)
+                        }
+                    }
+                }
+
+                null
+            }
         }
-    }
 
-    fun findEuiccChannelBySlotBlocking(slotId: Int): EuiccChannel? = runBlocking {
+    fun findEuiccChannelByPortBlocking(physicalSlotId: Int, portId: Int): EuiccChannel? = runBlocking {
         if (!checkPrivileges()) return@runBlocking null
         withContext(Dispatchers.IO) {
-            findEuiccChannelBySlot(slotId)
+            tm.uiccCardsInfoCompat.find { it.physicalSlotIndex == physicalSlotId }?.let { card ->
+                card.ports.find { it.portIndex == portId }?.let { tryOpenEuiccChannel(it) }
+            }
         }
     }
 
@@ -124,8 +131,10 @@ open class EuiccChannelManager(protected val context: Context) {
             ensureSEService()
 
             for (uiccInfo in tm.uiccCardsInfoCompat) {
-                if (tryOpenEuiccChannel(uiccInfo) != null) {
-                    Log.d(TAG, "Found eUICC on slot ${uiccInfo.physicalSlotIndex}")
+                for (port in uiccInfo.ports) {
+                    if (tryOpenEuiccChannel(port) != null) {
+                        Log.d(TAG, "Found eUICC on slot ${uiccInfo.physicalSlotIndex} port ${port.portIndex}")
+                    }
                 }
             }
         }

+ 6 - 5
app-common/src/main/java/im/angry/openeuicc/core/OmapiApduInterface.kt

@@ -3,6 +3,7 @@ package im.angry.openeuicc.core
 import android.se.omapi.Channel
 import android.se.omapi.SEService
 import android.se.omapi.Session
+import im.angry.openeuicc.util.UiccPortInfoCompat
 import net.typeblog.lpac_jni.ApduInterface
 import net.typeblog.lpac_jni.LocalProfileAssistant
 import net.typeblog.lpac_jni.impl.HttpInterfaceImpl
@@ -10,13 +11,13 @@ import net.typeblog.lpac_jni.impl.LocalProfileAssistantImpl
 
 class OmapiApduInterface(
     private val service: SEService,
-    private val info: EuiccChannelInfo
+    private val port: UiccPortInfoCompat
 ): ApduInterface {
     private lateinit var session: Session
     private lateinit var lastChannel: Channel
 
     override fun connect() {
-        session = service.getUiccReader(info.slotId + 1).openSession()
+        session = service.getUiccReader(port.logicalSlotIndex + 1).openSession()
     }
 
     override fun disconnect() {
@@ -50,9 +51,9 @@ class OmapiApduInterface(
 
 class OmapiChannel(
     service: SEService,
-    info: EuiccChannelInfo,
-) : EuiccChannel(info) {
+    port: UiccPortInfoCompat,
+) : EuiccChannel(port) {
     override val lpa: LocalProfileAssistant = LocalProfileAssistantImpl(
-        OmapiApduInterface(service, info),
+        OmapiApduInterface(service, port),
         HttpInterfaceImpl())
 }

+ 5 - 2
app-common/src/main/java/im/angry/openeuicc/ui/EuiccChannelFragmentUtils.kt

@@ -8,23 +8,26 @@ import im.angry.openeuicc.util.openEuiccApplication
 
 interface EuiccFragmentMarker
 
-fun <T> newInstanceEuicc(clazz: Class<T>, slotId: Int): T where T: Fragment, T: EuiccFragmentMarker {
+fun <T> newInstanceEuicc(clazz: Class<T>, slotId: Int, portId: Int): T where T: Fragment, T: EuiccFragmentMarker {
     val instance = clazz.newInstance()
     instance.arguments = Bundle().apply {
         putInt("slotId", slotId)
+        putInt("portId", portId)
     }
     return instance
 }
 
 val <T> T.slotId: Int where T: Fragment, T: EuiccFragmentMarker
     get() = requireArguments().getInt("slotId")
+val <T> T.portId: Int where T: Fragment, T: EuiccFragmentMarker
+    get() = requireArguments().getInt("portId")
 
 val <T> T.euiccChannelManager: EuiccChannelManager where T: Fragment, T: EuiccFragmentMarker
     get() = openEuiccApplication.euiccChannelManager
 
 val <T> T.channel: EuiccChannel where T: Fragment, T: EuiccFragmentMarker
     get() =
-        euiccChannelManager.findEuiccChannelBySlotBlocking(slotId)!!
+        euiccChannelManager.findEuiccChannelByPortBlocking(slotId, portId)!!
 
 interface EuiccProfilesChangedListener {
     fun onEuiccProfilesChanged()

+ 5 - 5
app-common/src/main/java/im/angry/openeuicc/ui/EuiccManagementFragment.kt

@@ -30,8 +30,8 @@ class EuiccManagementFragment : Fragment(), EuiccFragmentMarker, EuiccProfilesCh
     companion object {
         const val TAG = "EuiccManagementFragment"
 
-        fun newInstance(slotId: Int): EuiccManagementFragment =
-            newInstanceEuicc(EuiccManagementFragment::class.java, slotId)
+        fun newInstance(slotId: Int, portId: Int): EuiccManagementFragment =
+            newInstanceEuicc(EuiccManagementFragment::class.java, slotId, portId)
     }
 
     private lateinit var swipeRefresh: SwipeRefreshLayout
@@ -62,7 +62,7 @@ class EuiccManagementFragment : Fragment(), EuiccFragmentMarker, EuiccProfilesCh
             LinearLayoutManager(view.context, LinearLayoutManager.VERTICAL, false)
 
         fab.setOnClickListener {
-            ProfileDownloadFragment.newInstance(slotId)
+            ProfileDownloadFragment.newInstance(slotId, portId)
                 .show(childFragmentManager, ProfileDownloadFragment.TAG)
         }
     }
@@ -195,12 +195,12 @@ class EuiccManagementFragment : Fragment(), EuiccFragmentMarker, EuiccProfilesCh
                     true
                 }
                 R.id.rename -> {
-                    ProfileRenameFragment.newInstance(slotId, profile.iccid, profile.displayName)
+                    ProfileRenameFragment.newInstance(slotId, portId, profile.iccid, profile.displayName)
                         .show(childFragmentManager, ProfileRenameFragment.TAG)
                     true
                 }
                 R.id.delete -> {
-                    ProfileDeleteFragment.newInstance(slotId, profile.iccid, profile.displayName)
+                    ProfileDeleteFragment.newInstance(slotId, portId, profile.iccid, profile.displayName)
                         .show(childFragmentManager, ProfileDeleteFragment.TAG)
                     true
                 }

+ 1 - 1
app-common/src/main/java/im/angry/openeuicc/ui/MainActivity.kt

@@ -88,7 +88,7 @@ open class MainActivity : AppCompatActivity() {
         withContext(Dispatchers.Main) {
             manager.knownChannels.forEach { channel ->
                 spinnerAdapter.add(channel.name)
-                fragments.add(EuiccManagementFragment.newInstance(channel.slotId))
+                fragments.add(EuiccManagementFragment.newInstance(channel.slotId, channel.portId))
             }
 
             if (fragments.isNotEmpty()) {

+ 2 - 2
app-common/src/main/java/im/angry/openeuicc/ui/ProfileDeleteFragment.kt

@@ -16,8 +16,8 @@ class ProfileDeleteFragment : DialogFragment(), EuiccFragmentMarker {
     companion object {
         const val TAG = "ProfileDeleteFragment"
 
-        fun newInstance(slotId: Int, iccid: String, name: String): ProfileDeleteFragment {
-            val instance = newInstanceEuicc(ProfileDeleteFragment::class.java, slotId)
+        fun newInstance(slotId: Int, portId: Int, iccid: String, name: String): ProfileDeleteFragment {
+            val instance = newInstanceEuicc(ProfileDeleteFragment::class.java, slotId, portId)
             instance.requireArguments().apply {
                 putString("iccid", iccid)
                 putString("name", name)

+ 13 - 4
app-common/src/main/java/im/angry/openeuicc/ui/ProfileDownloadFragment.kt

@@ -1,5 +1,6 @@
 package im.angry.openeuicc.ui
 
+import android.annotation.SuppressLint
 import android.app.Dialog
 import android.os.Bundle
 import android.text.Editable
@@ -16,19 +17,20 @@ import com.google.android.material.textfield.TextInputLayout
 import com.journeyapps.barcodescanner.ScanContract
 import com.journeyapps.barcodescanner.ScanOptions
 import im.angry.openeuicc.common.R
+import im.angry.openeuicc.util.openEuiccApplication
 import im.angry.openeuicc.util.setWidthPercent
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.launch
 import kotlinx.coroutines.withContext
 import net.typeblog.lpac_jni.ProfileDownloadCallback
-import java.lang.Exception
+import kotlin.Exception
 
 class ProfileDownloadFragment : DialogFragment(), EuiccFragmentMarker, Toolbar.OnMenuItemClickListener {
     companion object {
         const val TAG = "ProfileDownloadFragment"
 
-        fun newInstance(slotId: Int): ProfileDownloadFragment =
-            newInstanceEuicc(ProfileDownloadFragment::class.java, slotId)
+        fun newInstance(slotId: Int, portId: Int): ProfileDownloadFragment =
+            newInstanceEuicc(ProfileDownloadFragment::class.java, slotId, portId)
     }
 
     private lateinit var toolbar: Toolbar
@@ -105,9 +107,16 @@ class ProfileDownloadFragment : DialogFragment(), EuiccFragmentMarker, Toolbar.O
         setWidthPercent(95)
     }
 
+    @SuppressLint("MissingPermission")
     override fun onStart() {
         super.onStart()
-        profileDownloadIMEI.editText!!.text = Editable.Factory.getInstance().newEditable(channel.imei)
+        profileDownloadIMEI.editText!!.text = Editable.Factory.getInstance().newEditable(
+            try {
+                openEuiccApplication.telephonyManager.getImei(channel.logicalSlotId)
+            } catch (e: Exception) {
+                ""
+            }
+        )
 
         lifecycleScope.launch(Dispatchers.IO) {
             // Fetch remaining NVRAM

+ 2 - 2
app-common/src/main/java/im/angry/openeuicc/ui/ProfileRenameFragment.kt

@@ -25,8 +25,8 @@ class ProfileRenameFragment : DialogFragment(), EuiccFragmentMarker {
     companion object {
         const val TAG = "ProfileRenameFragment"
 
-        fun newInstance(slotId: Int, iccid: String, currentName: String): ProfileRenameFragment {
-            val instance = newInstanceEuicc(ProfileRenameFragment::class.java, slotId)
+        fun newInstance(slotId: Int, portId: Int, iccid: String, currentName: String): ProfileRenameFragment {
+            val instance = newInstanceEuicc(ProfileRenameFragment::class.java, slotId, portId)
             instance.requireArguments().apply {
                 putString("iccid", iccid)
                 putString("currentName", currentName)

+ 8 - 0
app-common/src/main/java/im/angry/openeuicc/util/TelephonyCompat.kt

@@ -68,6 +68,14 @@ class UiccPortInfoCompat(private val _inner: Any?, val card: UiccCardInfoCompat)
             } else {
                 0
             }
+
+    val logicalSlotIndex: Int
+        get() =
+            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+                inner.logicalSlotIndex
+            } else {
+                card.physicalSlotIndex // logical is the same as physical below TIRAMISU
+            }
 }
 
 val TelephonyManager.uiccCardsInfoCompat: List<UiccCardInfoCompat>

+ 7 - 7
app/src/main/java/im/angry/openeuicc/core/PrivilegedEuiccChannelManager.kt

@@ -10,21 +10,21 @@ import java.lang.IllegalArgumentException
 class PrivilegedEuiccChannelManager(context: Context): EuiccChannelManager(context) {
     override fun checkPrivileges() = true // TODO: Implement proper system app check
 
-    override fun tryOpenEuiccChannelPrivileged(uiccInfo: UiccCardInfoCompat, channelInfo: EuiccChannelInfo): EuiccChannel? {
-        if (uiccInfo.isRemovable) {
+    override fun tryOpenEuiccChannelPrivileged(port: UiccPortInfoCompat): EuiccChannel? {
+        if (port.card.isRemovable) {
             // Attempt unprivileged (OMAPI) before TelephonyManager
             // but still try TelephonyManager in case OMAPI is broken
-            super.tryOpenEuiccChannelUnprivileged(uiccInfo, channelInfo)?.let { return it }
+            super.tryOpenEuiccChannelUnprivileged(port)?.let { return it }
         }
 
-        if (uiccInfo.isEuicc) {
-            Log.i(TAG, "Trying TelephonyManager for slot ${uiccInfo.physicalSlotIndex}")
+        if (port.card.isEuicc) {
+            Log.i(TAG, "Trying TelephonyManager for slot ${port.card.physicalSlotIndex} port ${port.portIndex}")
             // TODO: On Tiramisu, we should also connect all available "ports" for MEP support
             try {
-                return TelephonyManagerChannel(channelInfo, tm)
+                return TelephonyManagerChannel(port, tm)
             } catch (e: IllegalArgumentException) {
                 // Failed
-                Log.w(TAG, "TelephonyManager APDU interface unavailable for slot ${uiccInfo.physicalSlotIndex}, falling back")
+                Log.w(TAG, "TelephonyManager APDU interface unavailable for slot ${port.card.physicalSlotIndex} port ${port.portIndex}, falling back")
             }
         }
         return null

+ 8 - 8
app/src/main/java/im/angry/openeuicc/core/TelephonyManagerApduInterface.kt

@@ -9,7 +9,7 @@ import net.typeblog.lpac_jni.impl.HttpInterfaceImpl
 import net.typeblog.lpac_jni.impl.LocalProfileAssistantImpl
 
 class TelephonyManagerApduInterface(
-    private val info: EuiccChannelInfo,
+    private val port: UiccPortInfoCompat,
     private val tm: TelephonyManager
 ): ApduInterface {
     private var lastChannel: Int = -1
@@ -25,9 +25,9 @@ class TelephonyManagerApduInterface(
     override fun logicalChannelOpen(aid: ByteArray): Int {
         check(lastChannel == -1) { "Already initialized" }
         val hex = aid.encodeHex()
-        val channel = tm.iccOpenLogicalChannelBySlot(info.slotId, hex, 0)
+        val channel = tm.iccOpenLogicalChannelByPortCompat(port.card.physicalSlotIndex, port.portIndex, hex, 0)
         if (channel.status != IccOpenLogicalChannelResponse.STATUS_NO_ERROR || channel.channel == IccOpenLogicalChannelResponse.INVALID_CHANNEL) {
-            throw IllegalArgumentException("Cannot open logical channel " + hex + " via TelephonManager on slot " + info.slotId);
+            throw IllegalArgumentException("Cannot open logical channel $hex via TelephonManager on slot ${port.card.physicalSlotIndex} port ${port.portIndex}");
         }
         lastChannel = channel.channel
         return lastChannel
@@ -35,7 +35,7 @@ class TelephonyManagerApduInterface(
 
     override fun logicalChannelClose(handle: Int) {
         check(handle == lastChannel) { "Invalid channel handle " }
-        tm.iccCloseLogicalChannelBySlot(info.slotId, handle)
+        tm.iccCloseLogicalChannelByPortCompat(port.card.physicalSlotIndex, port.portIndex, handle)
         lastChannel = -1
     }
 
@@ -49,18 +49,18 @@ class TelephonyManagerApduInterface(
         val p3 = tx[4].toUByte().toInt()
         val p4 = tx.drop(5).toByteArray().encodeHex()
 
-        return tm.iccTransmitApduLogicalChannelBySlot(info.slotId, lastChannel,
+        return tm.iccTransmitApduLogicalChannelByPortCompat(port.card.physicalSlotIndex, port.portIndex, lastChannel,
             cla, instruction, p1, p2, p3, p4)?.decodeHex() ?: byteArrayOf()
     }
 
 }
 
 class TelephonyManagerChannel(
-    info: EuiccChannelInfo,
+    port: UiccPortInfoCompat,
     private val tm: TelephonyManager
-) : EuiccChannel(info) {
+) : EuiccChannel(port) {
     override val lpa: LocalProfileAssistant = LocalProfileAssistantImpl(
-        TelephonyManagerApduInterface(info, tm),
+        TelephonyManagerApduInterface(port, tm),
         HttpInterfaceImpl()
     )
 }