diff --git a/app-common/src/main/java/im/angry/openeuicc/core/DefaultEuiccChannelFactory.kt b/app-common/src/main/java/im/angry/openeuicc/core/DefaultEuiccChannelFactory.kt index e653a07..1cb3650 100644 --- a/app-common/src/main/java/im/angry/openeuicc/core/DefaultEuiccChannelFactory.kt +++ b/app-common/src/main/java/im/angry/openeuicc/core/DefaultEuiccChannelFactory.kt @@ -1,23 +1,14 @@ package im.angry.openeuicc.core import android.content.Context -import android.hardware.usb.UsbDevice -import android.hardware.usb.UsbInterface -import android.hardware.usb.UsbManager import android.se.omapi.SEService import android.util.Log -import im.angry.openeuicc.core.usb.UsbApduInterface -import im.angry.openeuicc.core.usb.getIoEndpoints import im.angry.openeuicc.util.* import java.lang.IllegalArgumentException open class DefaultEuiccChannelFactory(protected val context: Context) : EuiccChannelFactory { private var seService: SEService? = null - private val usbManager by lazy { - context.getSystemService(Context.USB_SERVICE) as UsbManager - } - private suspend fun ensureSEService() { if (seService == null || !seService!!.isConnected) { seService = connectSEService(context) @@ -45,17 +36,6 @@ open class DefaultEuiccChannelFactory(protected val context: Context) : EuiccCha return null } - override fun tryOpenUsbEuiccChannel(usbDevice: UsbDevice, usbInterface: UsbInterface): EuiccChannel? { - val (bulkIn, bulkOut) = usbInterface.getIoEndpoints() - if (bulkIn == null || bulkOut == null) return null - val conn = usbManager.openDevice(usbDevice) ?: return null - if (!conn.claimInterface(usbInterface, true)) return null - return EuiccChannel( - FakeUiccPortInfoCompat(FakeUiccCardInfoCompat(EuiccChannelManager.USB_CHANNEL_ID)), - UsbApduInterface(conn, bulkIn, bulkOut) - ) - } - override fun cleanup() { seService?.shutdown() seService = null diff --git a/app-common/src/main/java/im/angry/openeuicc/core/DefaultEuiccChannelManager.kt b/app-common/src/main/java/im/angry/openeuicc/core/DefaultEuiccChannelManager.kt index 0955959..1d6627d 100644 --- a/app-common/src/main/java/im/angry/openeuicc/core/DefaultEuiccChannelManager.kt +++ b/app-common/src/main/java/im/angry/openeuicc/core/DefaultEuiccChannelManager.kt @@ -1,11 +1,8 @@ package im.angry.openeuicc.core import android.content.Context -import android.hardware.usb.UsbDevice -import android.hardware.usb.UsbManager import android.telephony.SubscriptionManager import android.util.Log -import im.angry.openeuicc.core.usb.getSmartCardInterface import im.angry.openeuicc.di.AppContainer import im.angry.openeuicc.util.* import kotlinx.coroutines.Dispatchers @@ -26,18 +23,12 @@ open class DefaultEuiccChannelManager( private val channelCache = mutableListOf() - private var usbChannel: EuiccChannel? = null - private val lock = Mutex() protected val tm by lazy { appContainer.telephonyManager } - private val usbManager by lazy { - context.getSystemService(Context.USB_SERVICE) as UsbManager - } - private val euiccChannelFactory by lazy { appContainer.euiccChannelFactory } @@ -47,15 +38,6 @@ open class DefaultEuiccChannelManager( private suspend fun tryOpenEuiccChannel(port: UiccPortInfoCompat): EuiccChannel? { lock.withLock { - if (port.card.physicalSlotIndex == EuiccChannelManager.USB_CHANNEL_ID) { - return if (usbChannel != null && usbChannel!!.valid) { - usbChannel - } else { - usbChannel = null - null - } - } - val existing = channelCache.find { it.slotId == port.card.physicalSlotIndex && it.portId == port.portIndex } if (existing != null) { @@ -91,10 +73,6 @@ open class DefaultEuiccChannelManager( override fun findEuiccChannelBySlotBlocking(logicalSlotId: Int): EuiccChannel? = runBlocking { withContext(Dispatchers.IO) { - if (logicalSlotId == EuiccChannelManager.USB_CHANNEL_ID) { - return@withContext usbChannel - } - for (card in uiccCards) { for (port in card.ports) { if (port.logicalSlotIndex == logicalSlotId) { @@ -110,10 +88,6 @@ open class DefaultEuiccChannelManager( override fun findEuiccChannelByPhysicalSlotBlocking(physicalSlotId: Int): EuiccChannel? = runBlocking { withContext(Dispatchers.IO) { - if (physicalSlotId == EuiccChannelManager.USB_CHANNEL_ID) { - return@withContext usbChannel - } - for (card in uiccCards) { if (card.physicalSlotIndex != physicalSlotId) continue for (port in card.ports) { @@ -126,10 +100,6 @@ open class DefaultEuiccChannelManager( } override suspend fun findAllEuiccChannelsByPhysicalSlot(physicalSlotId: Int): List? { - if (physicalSlotId == EuiccChannelManager.USB_CHANNEL_ID) { - return usbChannel?.let { listOf(it) } - } - for (card in uiccCards) { if (card.physicalSlotIndex != physicalSlotId) continue return card.ports.mapNotNull { tryOpenEuiccChannel(it) } @@ -145,10 +115,6 @@ open class DefaultEuiccChannelManager( override suspend fun findEuiccChannelByPort(physicalSlotId: Int, portId: Int): EuiccChannel? = withContext(Dispatchers.IO) { - if (physicalSlotId == EuiccChannelManager.USB_CHANNEL_ID) { - return@withContext usbChannel - } - uiccCards.find { it.physicalSlotIndex == physicalSlotId }?.let { card -> card.ports.find { it.portIndex == portId }?.let { tryOpenEuiccChannel(it) } } @@ -196,37 +162,11 @@ open class DefaultEuiccChannelManager( } } - override suspend fun enumerateUsbEuiccChannel(): Pair = - withContext(Dispatchers.IO) { - usbManager.deviceList.values.forEach { device -> - Log.i(TAG, "Scanning USB device ${device.deviceId}:${device.vendorId}") - val iface = device.getSmartCardInterface() ?: return@forEach - // If we don't have permission, tell UI code that we found a candidate device, but we - // need permission to be able to do anything with it - if (!usbManager.hasPermission(device)) return@withContext Pair(device, null) - Log.i(TAG, "Found CCID interface on ${device.deviceId}:${device.vendorId}, and has permission; trying to open channel") - try { - val channel = euiccChannelFactory.tryOpenUsbEuiccChannel(device, iface) - if (channel != null && channel.lpa.valid) { - usbChannel = channel - return@withContext Pair(device, channel) - } - } catch (e: Exception) { - // Ignored -- skip forward - e.printStackTrace() - } - Log.i(TAG, "No valid eUICC channel found on USB device ${device.deviceId}:${device.vendorId}") - } - return@withContext Pair(null, null) - } - override fun invalidate() { for (channel in channelCache) { channel.close() } - usbChannel?.close() - usbChannel = null channelCache.clear() euiccChannelFactory.cleanup() } diff --git a/app-common/src/main/java/im/angry/openeuicc/core/EuiccChannelFactory.kt b/app-common/src/main/java/im/angry/openeuicc/core/EuiccChannelFactory.kt index fb5d95d..c8435dc 100644 --- a/app-common/src/main/java/im/angry/openeuicc/core/EuiccChannelFactory.kt +++ b/app-common/src/main/java/im/angry/openeuicc/core/EuiccChannelFactory.kt @@ -1,7 +1,5 @@ package im.angry.openeuicc.core -import android.hardware.usb.UsbDevice -import android.hardware.usb.UsbInterface import im.angry.openeuicc.util.* // This class is here instead of inside DI because it contains a bit more logic than just @@ -9,8 +7,6 @@ import im.angry.openeuicc.util.* interface EuiccChannelFactory { suspend fun tryOpenEuiccChannel(port: UiccPortInfoCompat): EuiccChannel? - fun tryOpenUsbEuiccChannel(usbDevice: UsbDevice, usbInterface: UsbInterface): EuiccChannel? - /** * Release all resources used by this EuiccChannelFactory * Note that the same instance may be reused; any resources allocated must be automatically diff --git a/app-common/src/main/java/im/angry/openeuicc/core/EuiccChannelManager.kt b/app-common/src/main/java/im/angry/openeuicc/core/EuiccChannelManager.kt index b21ccf6..5171779 100644 --- a/app-common/src/main/java/im/angry/openeuicc/core/EuiccChannelManager.kt +++ b/app-common/src/main/java/im/angry/openeuicc/core/EuiccChannelManager.kt @@ -1,7 +1,5 @@ package im.angry.openeuicc.core -import android.hardware.usb.UsbDevice - /** * EuiccChannelManager holds references to, and manages the lifecycles of, individual * APDU channels to SIM cards. The find* methods will create channels when needed, and @@ -13,25 +11,13 @@ import android.hardware.usb.UsbDevice * Holding references independent of EuiccChannelManagerService is unsupported. */ interface EuiccChannelManager { - companion object { - const val USB_CHANNEL_ID = 99 - } - /** - * Scan all possible _device internal_ sources for EuiccChannels, return them and have all + * Scan all possible sources for EuiccChannels, return them and have all * scanned channels cached; these channels will remain open for the entire lifetime of * this EuiccChannelManager object, unless disconnected externally or invalidate()'d */ suspend fun enumerateEuiccChannels(): List - /** - * Scan all possible USB devices for CCID readers that may contain eUICC cards. - * If found, try to open it for access, and add it to the internal EuiccChannel cache - * as a "port" with id 99. When user interaction is required to obtain permission - * to interact with the device, the second return value (EuiccChannel) will be null. - */ - suspend fun enumerateUsbEuiccChannel(): Pair - /** * Wait for a slot + port to reconnect (i.e. become valid again) * If the port is currently valid, this function will return immediately. diff --git a/app-common/src/main/java/im/angry/openeuicc/core/usb/UsbApduInterface.kt b/app-common/src/main/java/im/angry/openeuicc/core/usb/UsbApduInterface.kt deleted file mode 100644 index d181e53..0000000 --- a/app-common/src/main/java/im/angry/openeuicc/core/usb/UsbApduInterface.kt +++ /dev/null @@ -1,159 +0,0 @@ -package im.angry.openeuicc.core.usb - -import android.hardware.usb.UsbDeviceConnection -import android.hardware.usb.UsbEndpoint -import android.util.Log -import im.angry.openeuicc.util.* -import net.typeblog.lpac_jni.ApduInterface - -class UsbApduInterface( - private val conn: UsbDeviceConnection, - private val bulkIn: UsbEndpoint, - private val bulkOut: UsbEndpoint -): ApduInterface { - companion object { - private const val TAG = "UsbApduInterface" - } - - private lateinit var ccidDescription: UsbCcidDescription - private lateinit var transceiver: UsbCcidTransceiver - - private var channelId = -1 - - override fun connect() { - ccidDescription = UsbCcidDescription.fromRawDescriptors(conn.rawDescriptors)!! - - if (!ccidDescription.hasT0Protocol) { - throw IllegalArgumentException("Unsupported card reader; T=0 support is required") - } - - transceiver = UsbCcidTransceiver(conn, bulkIn, bulkOut, ccidDescription) - - try { - transceiver.iccPowerOn() - } catch (e: Exception) { - e.printStackTrace() - throw e - } - } - - override fun disconnect() { - conn.close() - } - - override fun logicalChannelOpen(aid: ByteArray): Int { - check(channelId == -1) { "Logical channel already opened" } - - // OPEN LOGICAL CHANNEL - val req = manageChannelCmd(true, 0) - Log.d(TAG, "OPEN LOGICAL CHANNEL: ${req.encodeHex()}") - - val resp = try { - transmitApduByChannel(req, 0) - } catch (e: Exception) { - e.printStackTrace() - return -1 - } - Log.d(TAG, "OPEN LOGICAL CHANNEL response: ${resp.encodeHex()}") - - return if (resp.size >= 2 && resp.sliceArray((resp.size - 2) until resp.size).contentEquals( - byteArrayOf(0x90.toByte(), 0x00) - ) - ) { - channelId = resp[0].toInt() - Log.d(TAG, "channelId = $channelId") - - // Then, select AID - val selectAid = selectByDfCmd(aid, channelId.toByte()) - Log.d(TAG, "Select DF command: ${selectAid.encodeHex()}") - val selectAidResp = transmitApduByChannel(selectAid, channelId.toByte()) - Log.d(TAG, "Select DF resp: ${selectAidResp.encodeHex()}") - channelId - } else { - -1 - } - } - - override fun logicalChannelClose(handle: Int) { - check(handle == channelId) { "Logical channel ID mismatch" } - check(channelId != -1) { "Logical channel is not opened" } - - // CLOSE LOGICAL CHANNEL - val req = manageChannelCmd(false, channelId.toByte()) - Log.d(TAG, "CLOSE LOGICAL CHANNEL: ${req.encodeHex()}") - - val resp = transmitApduByChannel(req, channelId.toByte()) - Log.d(TAG, "CLOSE LOGICAL CHANNEL response: ${resp.encodeHex()}") - - channelId = -1 - } - - override fun transmit(tx: ByteArray): ByteArray { - check(channelId != -1) { "Logical channel is not opened" } - Log.d(TAG, "USB APDU command: ${tx.encodeHex()}") - val resp = transmitApduByChannel(tx, channelId.toByte()) - Log.d(TAG, "USB APDU response: ${resp.encodeHex()}") - return resp - } - - override val valid: Boolean - get() = true - - private fun buildCmd(cla: Byte, ins: Byte, p1: Byte, p2: Byte, data: ByteArray?, le: Byte?) = - byteArrayOf(cla, ins, p1, p2).let { - if (data != null) { - it + data.size.toByte() + data - } else { - it - } - }.let { - if (le != null) { - it + byteArrayOf(le) - } else { - it - } - } - - private fun manageChannelCmd(open: Boolean, channel: Byte) = - if (open) { - buildCmd(0x00, 0x70, 0x00, 0x00, null, 0x01) - } else { - buildCmd(channel, 0x70, 0x80.toByte(), channel, null, null) - } - - private fun selectByDfCmd(aid: ByteArray, channel: Byte) = - buildCmd(channel, 0xA4.toByte(), 0x04, 0x00, aid, null) - - private fun transmitApduByChannel(tx: ByteArray, channel: Byte): ByteArray { - val realTx = tx.copyOf() - // OR the channel mask into the CLA byte - realTx[0] = ((realTx[0].toInt() and 0xFC) or channel.toInt()).toByte() - - var resp = transceiver.sendXfrBlock(realTx)!!.data!! - - if (resp.size >= 2) { - var sw1 = resp[resp.size - 2].toInt() and 0xFF - var sw2 = resp[resp.size - 1].toInt() and 0xFF - - if (sw1 == 0x6C) { - realTx[realTx.size - 1] = resp[resp.size - 1] - resp = transceiver.sendXfrBlock(realTx)!!.data!! - } else if (sw1 == 0x61) { - do { - val getResponseCmd = byteArrayOf( - realTx[0], 0xC0.toByte(), 0x00, 0x00, sw2.toByte() - ) - - val tmp = transceiver.sendXfrBlock(getResponseCmd)!!.data!! - - resp = resp.sliceArray(0 until (resp.size - 2)) + tmp - - sw1 = resp[resp.size - 2].toInt() and 0xFF - sw2 = resp[resp.size - 1].toInt() and 0xFF - } while (sw1 == 0x61) - } - } - - return resp - } -} \ No newline at end of file diff --git a/app-common/src/main/java/im/angry/openeuicc/core/usb/UsbCcidDescription.kt b/app-common/src/main/java/im/angry/openeuicc/core/usb/UsbCcidDescription.kt deleted file mode 100644 index 8a2ed39..0000000 --- a/app-common/src/main/java/im/angry/openeuicc/core/usb/UsbCcidDescription.kt +++ /dev/null @@ -1,104 +0,0 @@ -package im.angry.openeuicc.core.usb - -import java.nio.ByteBuffer -import java.nio.ByteOrder - -data class UsbCcidDescription( - private val bMaxSlotIndex: Byte, - private val bVoltageSupport: Byte, - private val dwProtocols: Int, - private val dwFeatures: Int -) { - companion object { - private const val DESCRIPTOR_LENGTH: Byte = 0x36 - private const val DESCRIPTOR_TYPE: Byte = 0x21 - - // dwFeatures Masks - private const val FEATURE_AUTOMATIC_VOLTAGE = 0x00008 - private const val FEATURE_AUTOMATIC_PPS = 0x00080 - - private const val FEATURE_EXCHANGE_LEVEL_TPDU = 0x10000 - private const val FEATURE_EXCHANGE_LEVEL_SHORT_APDU = 0x20000 - private const val FEATURE_EXCHAGE_LEVEL_EXTENDED_APDU = 0x40000 - - // bVoltageSupport Masks - private const val VOLTAGE_5V: Byte = 1 - private const val VOLTAGE_3V: Byte = 2 - private const val VOLTAGE_1_8V: Byte = 4 - - private const val SLOT_OFFSET = 4 - private const val FEATURES_OFFSET = 40 - private const val MASK_T0_PROTO = 1 - private const val MASK_T1_PROTO = 2 - - fun fromRawDescriptors(desc: ByteArray): UsbCcidDescription? { - var dwProtocols = 0 - var dwFeatures = 0 - var bMaxSlotIndex: Byte = 0 - var bVoltageSupport: Byte = 0 - - var hasCcidDescriptor = false - - val byteBuffer = ByteBuffer.wrap(desc).order(ByteOrder.LITTLE_ENDIAN) - - while (byteBuffer.hasRemaining()) { - byteBuffer.mark() - val len = byteBuffer.get() - val type = byteBuffer.get() - if (type == DESCRIPTOR_TYPE && len == DESCRIPTOR_LENGTH) { - byteBuffer.reset() - byteBuffer.position(byteBuffer.position() + SLOT_OFFSET) - bMaxSlotIndex = byteBuffer.get() - bVoltageSupport = byteBuffer.get() - dwProtocols = byteBuffer.int - byteBuffer.reset() - byteBuffer.position(byteBuffer.position() + FEATURES_OFFSET) - dwFeatures = byteBuffer.int - hasCcidDescriptor = true - break - } else { - byteBuffer.position(byteBuffer.position() + len - 2) - } - } - - return if (hasCcidDescriptor) { - UsbCcidDescription(bMaxSlotIndex, bVoltageSupport, dwProtocols, dwFeatures) - } else { - null - } - } - } - - enum class Voltage(powerOnValue: Int, mask: Int) { - AUTO(0, 0), _5V(1, VOLTAGE_5V.toInt()), _3V(2, VOLTAGE_3V.toInt()), _1_8V( - 3, - VOLTAGE_1_8V.toInt() - ); - - val mask = powerOnValue.toByte() - val powerOnValue = mask.toByte() - } - - private fun hasFeature(feature: Int): Boolean = - (dwFeatures and feature) != 0 - - val voltages: Array - get() = - if (hasFeature(FEATURE_AUTOMATIC_VOLTAGE)) { - arrayOf(Voltage.AUTO) - } else { - Voltage.values().mapNotNull { - if ((it.mask.toInt() and bVoltageSupport.toInt()) != 0) { - it - } else { - null - } - }.toTypedArray() - } - - val hasAutomaticPps: Boolean - get() = hasFeature(FEATURE_AUTOMATIC_PPS) - - val hasT0Protocol: Boolean - get() = (dwProtocols and MASK_T0_PROTO) != 0 -} \ No newline at end of file diff --git a/app-common/src/main/java/im/angry/openeuicc/core/usb/UsbCcidTransceiver.kt b/app-common/src/main/java/im/angry/openeuicc/core/usb/UsbCcidTransceiver.kt deleted file mode 100644 index ac2e1dc..0000000 --- a/app-common/src/main/java/im/angry/openeuicc/core/usb/UsbCcidTransceiver.kt +++ /dev/null @@ -1,348 +0,0 @@ -package im.angry.openeuicc.core.usb - -import android.hardware.usb.UsbDeviceConnection -import android.hardware.usb.UsbEndpoint -import android.os.SystemClock -import android.util.Log -import im.angry.openeuicc.util.* -import java.nio.ByteBuffer -import java.nio.ByteOrder - - -/** - * Provides raw, APDU-agnostic transmission to the CCID reader - * Adapted from - */ -class UsbCcidTransceiver( - private val usbConnection: UsbDeviceConnection, - private val usbBulkIn: UsbEndpoint, - private val usbBulkOut: UsbEndpoint, - private val usbCcidDescription: UsbCcidDescription -) { - companion object { - private const val TAG = "UsbCcidTransceiver" - - private const val CCID_HEADER_LENGTH = 10 - - private const val MESSAGE_TYPE_RDR_TO_PC_DATA_BLOCK = 0x80 - private const val MESSAGE_TYPE_PC_TO_RDR_ICC_POWER_ON = 0x62 - private const val MESSAGE_TYPE_PC_TO_RDR_ICC_POWER_OFF = 0x63 - private const val MESSAGE_TYPE_PC_TO_RDR_XFR_BLOCK = 0x6f - - private const val COMMAND_STATUS_SUCCESS: Byte = 0 - private const val COMMAND_STATUS_TIME_EXTENSION_RQUESTED: Byte = 2 - - /** - * Level Parameter: APDU is a single command. - * - * "the command APDU begins and ends with this command" - * -- DWG Smart-Card USB Integrated Circuit(s) Card Devices rev 1.0 - * § 6.1.1.3 - */ - const val LEVEL_PARAM_START_SINGLE_CMD_APDU: Short = 0x0000 - - /** - * Level Parameter: First APDU in a multi-command APDU. - * - * "the command APDU begins with this command, and continue in the - * next PC_to_RDR_XfrBlock" - * -- DWG Smart-Card USB Integrated Circuit(s) Card Devices rev 1.0 - * § 6.1.1.3 - */ - const val LEVEL_PARAM_START_MULTI_CMD_APDU: Short = 0x0001 - - /** - * Level Parameter: Final APDU in a multi-command APDU. - * - * "this abData field continues a command APDU and ends the command APDU" - * -- DWG Smart-Card USB Integrated Circuit(s) Card Devices rev 1.0 - * § 6.1.1.3 - */ - const val LEVEL_PARAM_END_MULTI_CMD_APDU: Short = 0x0002 - - /** - * Level Parameter: Next command in a multi-command APDU. - * - * "the abData field continues a command APDU and another block is to follow" - * -- DWG Smart-Card USB Integrated Circuit(s) Card Devices rev 1.0 - * § 6.1.1.3 - */ - const val LEVEL_PARAM_CONTINUE_MULTI_CMD_APDU: Short = 0x0003 - - /** - * Level Parameter: Request the device continue sending APDU. - * - * "empty abData field, continuation of response APDU is expected in the next - * RDR_to_PC_DataBlock" - * -- DWG Smart-Card USB Integrated Circuit(s) Card Devices rev 1.0 - * § 6.1.1.3 - */ - const val LEVEL_PARAM_CONTINUE_RESPONSE: Short = 0x0010 - - private const val SLOT_NUMBER = 0x00 - - private const val ICC_STATUS_SUCCESS: Byte = 0 - - private const val DEVICE_COMMUNICATE_TIMEOUT_MILLIS = 5000 - private const val DEVICE_SKIP_TIMEOUT_MILLIS = 100 - } - - data class UsbCcidErrorException(val msg: String, val errorResponse: CcidDataBlock) : - Exception(msg) - - data class CcidDataBlock( - val dwLength: Int, - val bSlot: Byte, - val bSeq: Byte, - val bStatus: Byte, - val bError: Byte, - val bChainParameter: Byte, - val data: ByteArray? - ) { - companion object { - fun parseHeaderFromBytes(headerBytes: ByteArray): CcidDataBlock { - val buf = ByteBuffer.wrap(headerBytes) - buf.order(ByteOrder.LITTLE_ENDIAN) - - val type = buf.get() - require(type == MESSAGE_TYPE_RDR_TO_PC_DATA_BLOCK.toByte()) { "Header has incorrect type value!" } - val dwLength = buf.int - val bSlot = buf.get() - val bSeq = buf.get() - val bStatus = buf.get() - val bError = buf.get() - val bChainParameter = buf.get() - - return CcidDataBlock(dwLength, bSlot, bSeq, bStatus, bError, bChainParameter, null) - } - } - - fun withData(d: ByteArray): CcidDataBlock { - require(data == null) { "Cannot add data twice" } - return CcidDataBlock(dwLength, bSlot, bSeq, bStatus, bError, bChainParameter, d) - } - - val iccStatus: Byte - get() = (bStatus.toInt() and 0x03).toByte() - - val commandStatus: Byte - get() = ((bStatus.toInt() shr 6) and 0x03).toByte() - - val isStatusTimeoutExtensionRequest: Boolean - get() = commandStatus == COMMAND_STATUS_TIME_EXTENSION_RQUESTED - - val isStatusSuccess: Boolean - get() = iccStatus == ICC_STATUS_SUCCESS && commandStatus == COMMAND_STATUS_SUCCESS - } - - val hasAutomaticPps = usbCcidDescription.hasAutomaticPps - - private val inputBuffer = ByteArray(usbBulkIn.maxPacketSize) - - private var currentSequenceNumber: Byte = 0 - - private fun sendRaw(data: ByteArray, offset: Int, length: Int) { - val tr1 = usbConnection.bulkTransfer( - usbBulkOut, data, offset, length, DEVICE_COMMUNICATE_TIMEOUT_MILLIS - ) - if (tr1 != length) { - throw UsbTransportException( - "USB error - failed to transmit data ($tr1/$length)" - ) - } - } - - private fun receiveDataBlock(expectedSequenceNumber: Byte): CcidDataBlock? { - var response: CcidDataBlock? - do { - response = receiveDataBlockImmediate(expectedSequenceNumber) - } while (response!!.isStatusTimeoutExtensionRequest) - if (!response.isStatusSuccess) { - throw UsbCcidErrorException("USB-CCID error!", response) - } - return response - } - - private fun receiveDataBlockImmediate(expectedSequenceNumber: Byte): CcidDataBlock? { - /* - * Some USB CCID devices (notably NitroKey 3) may time-out and need a subsequent poke to - * carry on communications. No particular reason why the number 3 was chosen. If we get a - * zero-sized reply (or a time-out), we try again. Clamped retries prevent an infinite loop - * if things really turn sour. - */ - var attempts = 3 - Log.d(TAG, "Receive data block immediate seq=$expectedSequenceNumber") - var readBytes: Int - do { - readBytes = usbConnection.bulkTransfer( - usbBulkIn, inputBuffer, inputBuffer.size, DEVICE_COMMUNICATE_TIMEOUT_MILLIS - ) - Log.d(TAG, "Received " + readBytes + " bytes: " + inputBuffer.encodeHex()) - } while (readBytes <= 0 && attempts-- > 0) - if (readBytes < CCID_HEADER_LENGTH) { - throw UsbTransportException("USB-CCID error - failed to receive CCID header") - } - if (inputBuffer[0] != MESSAGE_TYPE_RDR_TO_PC_DATA_BLOCK.toByte()) { - if (expectedSequenceNumber != inputBuffer[6]) { - throw UsbTransportException( - ((("USB-CCID error - bad CCID header, type " + inputBuffer[0]) + " (expected " + - MESSAGE_TYPE_RDR_TO_PC_DATA_BLOCK) + "), sequence number " + inputBuffer[6] - ) + " (expected " + - expectedSequenceNumber + ")" - ) - } - throw UsbTransportException( - "USB-CCID error - bad CCID header type " + inputBuffer[0] - ) - } - var result = CcidDataBlock.parseHeaderFromBytes(inputBuffer) - if (expectedSequenceNumber != result.bSeq) { - throw UsbTransportException( - ("USB-CCID error - expected sequence number " + - expectedSequenceNumber + ", got " + result) - ) - } - - val dataBuffer = ByteArray(result.dwLength) - var bufferedBytes = readBytes - CCID_HEADER_LENGTH - System.arraycopy(inputBuffer, CCID_HEADER_LENGTH, dataBuffer, 0, bufferedBytes) - while (bufferedBytes < dataBuffer.size) { - readBytes = usbConnection.bulkTransfer( - usbBulkIn, inputBuffer, inputBuffer.size, DEVICE_COMMUNICATE_TIMEOUT_MILLIS - ) - if (readBytes < 0) { - throw UsbTransportException( - "USB error - failed reading response data! Header: $result" - ) - } - System.arraycopy(inputBuffer, 0, dataBuffer, bufferedBytes, readBytes) - bufferedBytes += readBytes - } - result = result.withData(dataBuffer) - return result - } - - - private fun skipAvailableInput() { - var ignoredBytes: Int - do { - ignoredBytes = usbConnection.bulkTransfer( - usbBulkIn, inputBuffer, inputBuffer.size, DEVICE_SKIP_TIMEOUT_MILLIS - ) - if (ignoredBytes > 0) { - Log.e(TAG, "Skipped $ignoredBytes bytes") - } - } while (ignoredBytes > 0) - } - - /** - * Transmits XfrBlock - * 6.1.4 PC_to_RDR_XfrBlock - * - * @param payload payload to transmit - */ - fun sendXfrBlock(payload: ByteArray): CcidDataBlock? { - return sendXfrBlock(payload, LEVEL_PARAM_START_SINGLE_CMD_APDU) - } - - /** - * Receives a continued XfrBlock. Should be called when a multiblock response is indicated - * 6.1.4 PC_to_RDR_XfrBlock - */ - fun receiveContinuedResponse(): CcidDataBlock? { - return sendXfrBlock(ByteArray(0), LEVEL_PARAM_CONTINUE_RESPONSE) - } - - /** - * Transmits XfrBlock - * 6.1.4 PC_to_RDR_XfrBlock - * - * @param payload payload to transmit - * @param levelParam Level parameter - */ - private fun sendXfrBlock(payload: ByteArray, levelParam: Short): CcidDataBlock? { - val startTime = SystemClock.elapsedRealtime() - val l = payload.size - val sequenceNumber: Byte = currentSequenceNumber++ - val headerData = byteArrayOf( - MESSAGE_TYPE_PC_TO_RDR_XFR_BLOCK.toByte(), - l.toByte(), - (l shr 8).toByte(), - (l shr 16).toByte(), - (l shr 24).toByte(), - SLOT_NUMBER.toByte(), - sequenceNumber, - 0x00.toByte(), - (levelParam.toInt() and 0x00ff).toByte(), - (levelParam.toInt() shr 8).toByte() - ) - val data: ByteArray = headerData + payload - var sentBytes = 0 - while (sentBytes < data.size) { - val bytesToSend = Math.min(usbBulkOut.maxPacketSize, data.size - sentBytes) - sendRaw(data, sentBytes, bytesToSend) - sentBytes += bytesToSend - } - val ccidDataBlock = receiveDataBlock(sequenceNumber) - val elapsedTime = SystemClock.elapsedRealtime() - startTime - Log.d(TAG, "USB XferBlock call took " + elapsedTime + "ms") - return ccidDataBlock - } - - fun iccPowerOn(): CcidDataBlock { - val startTime = SystemClock.elapsedRealtime() - skipAvailableInput() - var response: CcidDataBlock? = null - for (v in usbCcidDescription.voltages) { - Log.v(TAG, "CCID: attempting to power on with voltage $v") - response = try { - iccPowerOnVoltage(v.powerOnValue) - } catch (e: UsbCcidErrorException) { - if (e.errorResponse.bError.toInt() == 7) { // Power select error - Log.v(TAG, "CCID: failed to power on with voltage $v") - iccPowerOff() - Log.v(TAG, "CCID: powered off") - continue - } - throw e - } - break - } - if (response == null) { - throw UsbTransportException("Couldn't power up ICC2") - } - val elapsedTime = SystemClock.elapsedRealtime() - startTime - Log.d( - TAG, - "Usb transport connected, took " + elapsedTime + "ms, ATR=" + - response.data?.encodeHex() - ) - return response - } - - private fun iccPowerOnVoltage(voltage: Byte): CcidDataBlock? { - val sequenceNumber = currentSequenceNumber++ - val iccPowerCommand = byteArrayOf( - MESSAGE_TYPE_PC_TO_RDR_ICC_POWER_ON.toByte(), - 0x00, 0x00, 0x00, 0x00, - SLOT_NUMBER.toByte(), - sequenceNumber, - voltage, - 0x00, 0x00 // reserved for future use - ) - sendRaw(iccPowerCommand, 0, iccPowerCommand.size) - return receiveDataBlock(sequenceNumber) - } - - private fun iccPowerOff() { - val sequenceNumber = currentSequenceNumber++ - val iccPowerCommand = byteArrayOf( - MESSAGE_TYPE_PC_TO_RDR_ICC_POWER_OFF.toByte(), - 0x00, 0x00, 0x00, 0x00, - 0x00, - sequenceNumber, - 0x00 - ) - sendRaw(iccPowerCommand, 0, iccPowerCommand.size) - } -} \ No newline at end of file diff --git a/app-common/src/main/java/im/angry/openeuicc/core/usb/UsbCcidUtils.kt b/app-common/src/main/java/im/angry/openeuicc/core/usb/UsbCcidUtils.kt deleted file mode 100644 index edca7a0..0000000 --- a/app-common/src/main/java/im/angry/openeuicc/core/usb/UsbCcidUtils.kt +++ /dev/null @@ -1,36 +0,0 @@ -// Adapted from -package im.angry.openeuicc.core.usb - -import android.hardware.usb.UsbConstants -import android.hardware.usb.UsbDevice -import android.hardware.usb.UsbEndpoint -import android.hardware.usb.UsbInterface - -class UsbTransportException(msg: String) : Exception(msg) - -fun UsbInterface.getIoEndpoints(): Pair { - var bulkIn: UsbEndpoint? = null - var bulkOut: UsbEndpoint? = null - for (i in 0 until endpointCount) { - val endpoint = getEndpoint(i) - if (endpoint.type != UsbConstants.USB_ENDPOINT_XFER_BULK) { - continue - } - if (endpoint.direction == UsbConstants.USB_DIR_IN) { - bulkIn = endpoint - } else if (endpoint.direction == UsbConstants.USB_DIR_OUT) { - bulkOut = endpoint - } - } - return Pair(bulkIn, bulkOut) -} - -fun UsbDevice.getSmartCardInterface(): UsbInterface? { - for (i in 0 until interfaceCount) { - val anInterface = getInterface(i) - if (anInterface.interfaceClass == UsbConstants.USB_CLASS_CSCID) { - return anInterface - } - } - return null -} \ No newline at end of file diff --git a/app-common/src/main/java/im/angry/openeuicc/ui/MainActivity.kt b/app-common/src/main/java/im/angry/openeuicc/ui/MainActivity.kt index 4641732..9befe57 100644 --- a/app-common/src/main/java/im/angry/openeuicc/ui/MainActivity.kt +++ b/app-common/src/main/java/im/angry/openeuicc/ui/MainActivity.kt @@ -1,14 +1,6 @@ package im.angry.openeuicc.ui -import android.annotation.SuppressLint -import android.app.PendingIntent -import android.content.BroadcastReceiver -import android.content.Context import android.content.Intent -import android.content.IntentFilter -import android.hardware.usb.UsbDevice -import android.hardware.usb.UsbManager -import android.os.Build import android.os.Bundle import android.telephony.TelephonyManager import android.util.Log @@ -20,7 +12,6 @@ import android.widget.ArrayAdapter import android.widget.Spinner import androidx.lifecycle.lifecycleScope import im.angry.openeuicc.common.R -import im.angry.openeuicc.core.EuiccChannel import im.angry.openeuicc.util.* import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch @@ -29,7 +20,6 @@ import kotlinx.coroutines.withContext open class MainActivity : BaseEuiccAccessActivity(), OpenEuiccContextMarker { companion object { const val TAG = "MainActivity" - const val ACTION_USB_PERMISSION = "im.angry.openeuicc.USB_PERMISSION" } private lateinit var spinnerAdapter: ArrayAdapter @@ -40,28 +30,6 @@ open class MainActivity : BaseEuiccAccessActivity(), OpenEuiccContextMarker { protected lateinit var tm: TelephonyManager - private val usbManager: UsbManager by lazy { - getSystemService(USB_SERVICE) as UsbManager - } - - private var usbDevice: UsbDevice? = null - private var usbChannel: EuiccChannel? = null - - private lateinit var usbPendingIntent: PendingIntent - - private val usbPermissionReceiver = object : BroadcastReceiver() { - override fun onReceive(context: Context?, intent: Intent?) { - if (intent?.action == ACTION_USB_PERMISSION) { - if (usbDevice != null && usbManager.hasPermission(usbDevice)) { - lifecycleScope.launch(Dispatchers.Main) { - switchToUsbFragmentIfPossible() - } - } - } - } - } - - @SuppressLint("WrongConstant", "UnspecifiedRegisterReceiverFlag") override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) @@ -75,15 +43,6 @@ open class MainActivity : BaseEuiccAccessActivity(), OpenEuiccContextMarker { tm = telephonyManager spinnerAdapter = ArrayAdapter(this, R.layout.spinner_item) - - usbPendingIntent = PendingIntent.getBroadcast(this, 0, - Intent(ACTION_USB_PERMISSION), PendingIntent.FLAG_IMMUTABLE) - val filter = IntentFilter(ACTION_USB_PERMISSION) - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - registerReceiver(usbPermissionReceiver, filter, Context.RECEIVER_EXPORTED) - } else { - registerReceiver(usbPermissionReceiver, filter) - } } override fun onCreateOptionsMenu(menu: Menu): Boolean { @@ -103,15 +62,8 @@ open class MainActivity : BaseEuiccAccessActivity(), OpenEuiccContextMarker { position: Int, id: Long ) { - if (position < fragments.size) { - supportFragmentManager.beginTransaction() - .replace(R.id.fragment_root, fragments[position]).commit() - } else if (position == fragments.size) { - // If we are at the last position, this is the USB device - lifecycleScope.launch(Dispatchers.Main) { - switchToUsbFragmentIfPossible() - } - } + supportFragmentManager.beginTransaction() + .replace(R.id.fragment_root, fragments[position]).commit() } override fun onNothingSelected(parent: AdapterView<*>?) { @@ -154,22 +106,12 @@ open class MainActivity : BaseEuiccAccessActivity(), OpenEuiccContextMarker { } } - withContext(Dispatchers.IO) { - val res = euiccChannelManager.enumerateUsbEuiccChannel() - usbDevice = res.first - usbChannel = res.second - } - withContext(Dispatchers.Main) { knownChannels.sortedBy { it.logicalSlotId }.forEach { channel -> spinnerAdapter.add(getString(R.string.channel_name_format, channel.logicalSlotId)) fragments.add(appContainer.uiComponentFactory.createEuiccManagementFragment(channel)) } - // If USB readers exist, add them at the very last - // The adapter logic depends on this assumption - usbDevice?.let { spinnerAdapter.add(it.productName) } - if (fragments.isNotEmpty()) { if (this@MainActivity::spinner.isInitialized) { spinnerItem.isVisible = true @@ -178,27 +120,4 @@ open class MainActivity : BaseEuiccAccessActivity(), OpenEuiccContextMarker { } } } - - private suspend fun switchToUsbFragmentIfPossible() { - if (usbDevice != null && usbChannel == null) { - if (!usbManager.hasPermission(usbDevice)) { - usbManager.requestPermission(usbDevice, usbPendingIntent) - return - } else { - val (device, channel) = withContext(Dispatchers.IO) { - euiccChannelManager.enumerateUsbEuiccChannel() - } - - if (device != null && channel != null) { - usbDevice = device - usbChannel = channel - } - } - } - - if (usbChannel != null) { - supportFragmentManager.beginTransaction().replace(R.id.fragment_root, - appContainer.uiComponentFactory.createEuiccManagementFragment(usbChannel!!)).commit() - } - } } \ No newline at end of file diff --git a/app-common/src/main/java/im/angry/openeuicc/ui/NotificationsActivity.kt b/app-common/src/main/java/im/angry/openeuicc/ui/NotificationsActivity.kt index 9758d18..744a87d 100644 --- a/app-common/src/main/java/im/angry/openeuicc/ui/NotificationsActivity.kt +++ b/app-common/src/main/java/im/angry/openeuicc/ui/NotificationsActivity.kt @@ -180,9 +180,8 @@ class NotificationsActivity: BaseEuiccAccessActivity(), OpenEuiccContextMarker { withContext(Dispatchers.IO) { euiccChannel.lpa.handleNotification(notification.inner.seqNumber) } - - refresh() } + refresh() true } R.id.notification_delete -> { @@ -190,9 +189,8 @@ class NotificationsActivity: BaseEuiccAccessActivity(), OpenEuiccContextMarker { withContext(Dispatchers.IO) { euiccChannel.lpa.deleteNotification(notification.inner.seqNumber) } - - refresh() } + refresh() true } else -> false diff --git a/app/src/main/java/im/angry/openeuicc/util/PrivilegedTelephonyUtils.kt b/app/src/main/java/im/angry/openeuicc/util/PrivilegedTelephonyUtils.kt index ae7294a..3c30bce 100644 --- a/app/src/main/java/im/angry/openeuicc/util/PrivilegedTelephonyUtils.kt +++ b/app/src/main/java/im/angry/openeuicc/util/PrivilegedTelephonyUtils.kt @@ -74,12 +74,11 @@ fun SubscriptionManager.tryRefreshCachedEuiccInfo(cardId: Int) { } // Every EuiccChannel we use here should be backed by a RealUiccPortInfoCompat -// except when it is from a USB card reader val EuiccChannel.removable - get() = (port as? RealUiccPortInfoCompat)?.card?.isRemovable ?: true + get() = (port as RealUiccPortInfoCompat).card.isRemovable val EuiccChannel.cardId - get() = (port as? RealUiccPortInfoCompat)?.card?.cardId ?: -1 + get() = (port as RealUiccPortInfoCompat).card.cardId val EuiccChannel.isMEP - get() = (port as? RealUiccPortInfoCompat)?.card?.isMultipleEnabledProfilesSupported ?: false \ No newline at end of file + get() = (port as RealUiccPortInfoCompat).card.isMultipleEnabledProfilesSupported \ No newline at end of file