Compare commits
8 commits
Author | SHA1 | Date | |
---|---|---|---|
2f1c17c58a | |||
7609b74a37 | |||
970dc19462 | |||
be69c88228 | |||
5695f81e0d | |||
aba844c09c | |||
5dd9e40c25 | |||
6bb05d910b |
23 changed files with 372 additions and 113 deletions
|
@ -20,7 +20,8 @@ open class DefaultEuiccChannelFactory(protected val context: Context) : EuiccCha
|
||||||
|
|
||||||
override suspend fun tryOpenEuiccChannel(
|
override suspend fun tryOpenEuiccChannel(
|
||||||
port: UiccPortInfoCompat,
|
port: UiccPortInfoCompat,
|
||||||
isdrAid: ByteArray
|
isdrAid: ByteArray,
|
||||||
|
seId: EuiccChannel.SecureElementId,
|
||||||
): EuiccChannel? {
|
): EuiccChannel? {
|
||||||
if (port.portIndex != 0) {
|
if (port.portIndex != 0) {
|
||||||
Log.w(
|
Log.w(
|
||||||
|
@ -46,6 +47,7 @@ open class DefaultEuiccChannelFactory(protected val context: Context) : EuiccCha
|
||||||
context.preferenceRepository.verboseLoggingFlow
|
context.preferenceRepository.verboseLoggingFlow
|
||||||
),
|
),
|
||||||
isdrAid,
|
isdrAid,
|
||||||
|
seId,
|
||||||
context.preferenceRepository.verboseLoggingFlow,
|
context.preferenceRepository.verboseLoggingFlow,
|
||||||
context.preferenceRepository.ignoreTLSCertificateFlow,
|
context.preferenceRepository.ignoreTLSCertificateFlow,
|
||||||
).also {
|
).also {
|
||||||
|
@ -65,7 +67,8 @@ open class DefaultEuiccChannelFactory(protected val context: Context) : EuiccCha
|
||||||
|
|
||||||
override fun tryOpenUsbEuiccChannel(
|
override fun tryOpenUsbEuiccChannel(
|
||||||
ccidCtx: UsbCcidContext,
|
ccidCtx: UsbCcidContext,
|
||||||
isdrAid: ByteArray
|
isdrAid: ByteArray,
|
||||||
|
seId: EuiccChannel.SecureElementId
|
||||||
): EuiccChannel? {
|
): EuiccChannel? {
|
||||||
try {
|
try {
|
||||||
return EuiccChannelImpl(
|
return EuiccChannelImpl(
|
||||||
|
@ -76,6 +79,7 @@ open class DefaultEuiccChannelFactory(protected val context: Context) : EuiccCha
|
||||||
ccidCtx
|
ccidCtx
|
||||||
),
|
),
|
||||||
isdrAid,
|
isdrAid,
|
||||||
|
seId,
|
||||||
context.preferenceRepository.verboseLoggingFlow,
|
context.preferenceRepository.verboseLoggingFlow,
|
||||||
context.preferenceRepository.ignoreTLSCertificateFlow,
|
context.preferenceRepository.ignoreTLSCertificateFlow,
|
||||||
)
|
)
|
||||||
|
|
|
@ -32,7 +32,7 @@ open class DefaultEuiccChannelManager(
|
||||||
|
|
||||||
private val channelCache = mutableListOf<EuiccChannel>()
|
private val channelCache = mutableListOf<EuiccChannel>()
|
||||||
|
|
||||||
private var usbChannel: EuiccChannel? = null
|
private var usbChannels = mutableListOf<EuiccChannel>()
|
||||||
|
|
||||||
private val lock = Mutex()
|
private val lock = Mutex()
|
||||||
|
|
||||||
|
@ -51,15 +51,20 @@ open class DefaultEuiccChannelManager(
|
||||||
protected open val uiccCards: Collection<UiccCardInfoCompat>
|
protected open val uiccCards: Collection<UiccCardInfoCompat>
|
||||||
get() = (0..<tm.activeModemCountCompat).map { FakeUiccCardInfoCompat(it) }
|
get() = (0..<tm.activeModemCountCompat).map { FakeUiccCardInfoCompat(it) }
|
||||||
|
|
||||||
private suspend inline fun tryOpenChannelFirstValidAid(openFn: (ByteArray) -> EuiccChannel?): EuiccChannel? {
|
private suspend inline fun tryOpenChannelWithKnownAids(openFn: (ByteArray, EuiccChannel.SecureElementId) -> EuiccChannel?): List<EuiccChannel> {
|
||||||
val isdrAidList =
|
val isdrAidList =
|
||||||
parseIsdrAidList(appContainer.preferenceRepository.isdrAidListFlow.first())
|
parseIsdrAidList(appContainer.preferenceRepository.isdrAidListFlow.first())
|
||||||
|
var seId = 0
|
||||||
|
|
||||||
return isdrAidList.firstNotNullOfOrNull {
|
return isdrAidList.mapNotNull {
|
||||||
Log.i(TAG, "Opening channel, trying ISDR AID ${it.encodeHex()}")
|
Log.i(
|
||||||
|
TAG,
|
||||||
|
"Opening channel, trying ISDR AID ${it.encodeHex()}, this will be seId $seId"
|
||||||
|
)
|
||||||
|
|
||||||
openFn(it)?.let { channel ->
|
openFn(it, EuiccChannel.SecureElementId.createFromInt(seId))?.let { channel ->
|
||||||
if (channel.valid) {
|
if (channel.valid) {
|
||||||
|
seId += 1
|
||||||
channel
|
channel
|
||||||
} else {
|
} else {
|
||||||
channel.close()
|
channel.close()
|
||||||
|
@ -69,19 +74,18 @@ open class DefaultEuiccChannelManager(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun tryOpenEuiccChannel(port: UiccPortInfoCompat): EuiccChannel? {
|
private suspend fun tryOpenEuiccChannel(
|
||||||
|
port: UiccPortInfoCompat,
|
||||||
|
seId: EuiccChannel.SecureElementId = EuiccChannel.SecureElementId.DEFAULT
|
||||||
|
): EuiccChannel? {
|
||||||
lock.withLock {
|
lock.withLock {
|
||||||
if (port.card.physicalSlotIndex == EuiccChannelManager.USB_CHANNEL_ID) {
|
if (port.card.physicalSlotIndex == EuiccChannelManager.USB_CHANNEL_ID) {
|
||||||
return if (usbChannel != null && usbChannel!!.valid) {
|
// We only compare seId because we assume we can only open 1 card from USB
|
||||||
usbChannel
|
return usbChannels.find { it.seId == seId }
|
||||||
} else {
|
|
||||||
usbChannel = null
|
|
||||||
null
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val existing =
|
val existing =
|
||||||
channelCache.find { it.slotId == port.card.physicalSlotIndex && it.portId == port.portIndex }
|
channelCache.find { it.slotId == port.card.physicalSlotIndex && it.portId == port.portIndex && it.seId == seId }
|
||||||
if (existing != null) {
|
if (existing != null) {
|
||||||
if (existing.valid && port.logicalSlotIndex == existing.logicalSlotId) {
|
if (existing.valid && port.logicalSlotIndex == existing.logicalSlotId) {
|
||||||
return existing
|
return existing
|
||||||
|
@ -96,12 +100,18 @@ open class DefaultEuiccChannelManager(
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
val channel =
|
val channels =
|
||||||
tryOpenChannelFirstValidAid { euiccChannelFactory.tryOpenEuiccChannel(port, it) }
|
tryOpenChannelWithKnownAids { isdrAid, seId ->
|
||||||
|
euiccChannelFactory.tryOpenEuiccChannel(
|
||||||
|
port,
|
||||||
|
isdrAid,
|
||||||
|
seId
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
if (channel != null) {
|
if (channels.isNotEmpty()) {
|
||||||
channelCache.add(channel)
|
channelCache.addAll(channels)
|
||||||
return channel
|
return channels.find { it.seId == seId }
|
||||||
} else {
|
} else {
|
||||||
Log.i(
|
Log.i(
|
||||||
TAG,
|
TAG,
|
||||||
|
@ -112,10 +122,13 @@ open class DefaultEuiccChannelManager(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected suspend fun findEuiccChannelByLogicalSlot(logicalSlotId: Int): EuiccChannel? =
|
protected suspend fun findEuiccChannelByLogicalSlot(
|
||||||
|
logicalSlotId: Int,
|
||||||
|
seId: EuiccChannel.SecureElementId = EuiccChannel.SecureElementId.DEFAULT
|
||||||
|
): EuiccChannel? =
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
if (logicalSlotId == EuiccChannelManager.USB_CHANNEL_ID) {
|
if (logicalSlotId == EuiccChannelManager.USB_CHANNEL_ID) {
|
||||||
return@withContext usbChannel
|
return@withContext usbChannels.find { it.seId == seId }
|
||||||
}
|
}
|
||||||
|
|
||||||
for (card in uiccCards) {
|
for (card in uiccCards) {
|
||||||
|
@ -131,7 +144,7 @@ open class DefaultEuiccChannelManager(
|
||||||
|
|
||||||
private suspend fun findAllEuiccChannelsByPhysicalSlot(physicalSlotId: Int): List<EuiccChannel>? {
|
private suspend fun findAllEuiccChannelsByPhysicalSlot(physicalSlotId: Int): List<EuiccChannel>? {
|
||||||
if (physicalSlotId == EuiccChannelManager.USB_CHANNEL_ID) {
|
if (physicalSlotId == EuiccChannelManager.USB_CHANNEL_ID) {
|
||||||
return usbChannel?.let { listOf(it) }
|
return usbChannels.ifEmpty { null }
|
||||||
}
|
}
|
||||||
|
|
||||||
for (card in uiccCards) {
|
for (card in uiccCards) {
|
||||||
|
@ -142,14 +155,18 @@ open class DefaultEuiccChannelManager(
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun findEuiccChannelByPort(physicalSlotId: Int, portId: Int): EuiccChannel? =
|
private suspend fun findEuiccChannelByPort(
|
||||||
|
physicalSlotId: Int,
|
||||||
|
portId: Int,
|
||||||
|
seId: EuiccChannel.SecureElementId = EuiccChannel.SecureElementId.DEFAULT
|
||||||
|
): EuiccChannel? =
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
if (physicalSlotId == EuiccChannelManager.USB_CHANNEL_ID) {
|
if (physicalSlotId == EuiccChannelManager.USB_CHANNEL_ID) {
|
||||||
return@withContext usbChannel
|
return@withContext usbChannels.find { it.seId == seId }
|
||||||
}
|
}
|
||||||
|
|
||||||
uiccCards.find { it.physicalSlotIndex == physicalSlotId }?.let { card ->
|
uiccCards.find { it.physicalSlotIndex == physicalSlotId }?.let { card ->
|
||||||
card.ports.find { it.portIndex == portId }?.let { tryOpenEuiccChannel(it) }
|
card.ports.find { it.portIndex == portId }?.let { tryOpenEuiccChannel(it, seId) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -168,15 +185,17 @@ open class DefaultEuiccChannelManager(
|
||||||
return@withContext listOf(0)
|
return@withContext listOf(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
findAllEuiccChannelsByPhysicalSlot(physicalSlotId)?.map { it.portId } ?: listOf()
|
findAllEuiccChannelsByPhysicalSlot(physicalSlotId)?.map { it.portId }?.toSet()?.toList()
|
||||||
|
?: listOf()
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun <R> withEuiccChannel(
|
override suspend fun <R> withEuiccChannel(
|
||||||
physicalSlotId: Int,
|
physicalSlotId: Int,
|
||||||
portId: Int,
|
portId: Int,
|
||||||
|
seId: EuiccChannel.SecureElementId,
|
||||||
fn: suspend (EuiccChannel) -> R
|
fn: suspend (EuiccChannel) -> R
|
||||||
): R {
|
): R {
|
||||||
val channel = findEuiccChannelByPort(physicalSlotId, portId)
|
val channel = findEuiccChannelByPort(physicalSlotId, portId, seId)
|
||||||
?: throw EuiccChannelManager.EuiccChannelNotFoundException()
|
?: throw EuiccChannelManager.EuiccChannelNotFoundException()
|
||||||
val wrapper = EuiccChannelWrapper(channel)
|
val wrapper = EuiccChannelWrapper(channel)
|
||||||
try {
|
try {
|
||||||
|
@ -190,9 +209,10 @@ open class DefaultEuiccChannelManager(
|
||||||
|
|
||||||
override suspend fun <R> withEuiccChannel(
|
override suspend fun <R> withEuiccChannel(
|
||||||
logicalSlotId: Int,
|
logicalSlotId: Int,
|
||||||
|
seId: EuiccChannel.SecureElementId,
|
||||||
fn: suspend (EuiccChannel) -> R
|
fn: suspend (EuiccChannel) -> R
|
||||||
): R {
|
): R {
|
||||||
val channel = findEuiccChannelByLogicalSlot(logicalSlotId)
|
val channel = findEuiccChannelByLogicalSlot(logicalSlotId, seId)
|
||||||
?: throw EuiccChannelManager.EuiccChannelNotFoundException()
|
?: throw EuiccChannelManager.EuiccChannelNotFoundException()
|
||||||
val wrapper = EuiccChannelWrapper(channel)
|
val wrapper = EuiccChannelWrapper(channel)
|
||||||
try {
|
try {
|
||||||
|
@ -206,8 +226,8 @@ open class DefaultEuiccChannelManager(
|
||||||
|
|
||||||
override suspend fun waitForReconnect(physicalSlotId: Int, portId: Int, timeoutMillis: Long) {
|
override suspend fun waitForReconnect(physicalSlotId: Int, portId: Int, timeoutMillis: Long) {
|
||||||
if (physicalSlotId == EuiccChannelManager.USB_CHANNEL_ID) {
|
if (physicalSlotId == EuiccChannelManager.USB_CHANNEL_ID) {
|
||||||
usbChannel?.close()
|
usbChannels.forEach { it.close() }
|
||||||
usbChannel = null
|
usbChannels.clear()
|
||||||
} else {
|
} else {
|
||||||
// If there is already a valid channel, we close it proactively
|
// If there is already a valid channel, we close it proactively
|
||||||
// Sometimes the current channel can linger on for a bit even after it should have become invalid
|
// Sometimes the current channel can linger on for a bit even after it should have become invalid
|
||||||
|
@ -223,7 +243,7 @@ open class DefaultEuiccChannelManager(
|
||||||
// tryOpenUsbEuiccChannel() will always try to reopen the channel, even if
|
// tryOpenUsbEuiccChannel() will always try to reopen the channel, even if
|
||||||
// a USB channel already exists
|
// a USB channel already exists
|
||||||
tryOpenUsbEuiccChannel()
|
tryOpenUsbEuiccChannel()
|
||||||
usbChannel!!
|
usbChannels.getOrNull(0)!!
|
||||||
} else {
|
} else {
|
||||||
// tryOpenEuiccChannel() will automatically dispose of invalid channels
|
// tryOpenEuiccChannel() will automatically dispose of invalid channels
|
||||||
// and recreate when needed
|
// and recreate when needed
|
||||||
|
@ -264,6 +284,20 @@ open class DefaultEuiccChannelManager(
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
override fun flowEuiccSecureElements(
|
||||||
|
slotId: Int,
|
||||||
|
portId: Int
|
||||||
|
): Flow<EuiccChannel.SecureElementId> = flow {
|
||||||
|
// Emit the "default" channel first
|
||||||
|
// TODO: This function below should really return a list, not just one SE
|
||||||
|
findEuiccChannelByPort(slotId, portId, seId = EuiccChannel.SecureElementId.DEFAULT)?.let {
|
||||||
|
emit(EuiccChannel.SecureElementId.DEFAULT)
|
||||||
|
|
||||||
|
channelCache.filter { it.slotId == slotId && it.portId == portId && it.seId != EuiccChannel.SecureElementId.DEFAULT }
|
||||||
|
.forEach { emit(it.seId) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override suspend fun tryOpenUsbEuiccChannel(): Pair<UsbDevice?, Boolean> =
|
override suspend fun tryOpenUsbEuiccChannel(): Pair<UsbDevice?, Boolean> =
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
usbManager.deviceList.values.forEach { device ->
|
usbManager.deviceList.values.forEach { device ->
|
||||||
|
@ -277,15 +311,17 @@ open class DefaultEuiccChannelManager(
|
||||||
"Found CCID interface on ${device.deviceId}:${device.vendorId}, and has permission; trying to open channel"
|
"Found CCID interface on ${device.deviceId}:${device.vendorId}, and has permission; trying to open channel"
|
||||||
)
|
)
|
||||||
|
|
||||||
val ccidCtx = UsbCcidContext.createFromUsbDevice(context, device, iface) ?: return@forEach
|
val ccidCtx =
|
||||||
|
UsbCcidContext.createFromUsbDevice(context, device, iface) ?: return@forEach
|
||||||
|
|
||||||
try {
|
try {
|
||||||
val channel = tryOpenChannelFirstValidAid {
|
val channels = tryOpenChannelWithKnownAids { isdrAid, seId ->
|
||||||
euiccChannelFactory.tryOpenUsbEuiccChannel(ccidCtx, it)
|
euiccChannelFactory.tryOpenUsbEuiccChannel(ccidCtx, isdrAid, seId)
|
||||||
}
|
}
|
||||||
if (channel != null && channel.lpa.valid) {
|
if (channels.isNotEmpty() && channels[0].valid) {
|
||||||
ccidCtx.allowDisconnect = true
|
ccidCtx.allowDisconnect = true
|
||||||
usbChannel = channel
|
usbChannels.clear()
|
||||||
|
usbChannels.addAll(channels)
|
||||||
return@withContext Pair(device, true)
|
return@withContext Pair(device, true)
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
|
@ -309,8 +345,8 @@ open class DefaultEuiccChannelManager(
|
||||||
channel.close()
|
channel.close()
|
||||||
}
|
}
|
||||||
|
|
||||||
usbChannel?.close()
|
usbChannels.forEach { it.close() }
|
||||||
usbChannel = null
|
usbChannels.clear()
|
||||||
channelCache.clear()
|
channelCache.clear()
|
||||||
euiccChannelFactory.cleanup()
|
euiccChannelFactory.cleanup()
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
package im.angry.openeuicc.core
|
package im.angry.openeuicc.core
|
||||||
|
|
||||||
|
import android.os.Parcel
|
||||||
|
import android.os.Parcelable
|
||||||
import im.angry.openeuicc.util.*
|
import im.angry.openeuicc.util.*
|
||||||
import net.typeblog.lpac_jni.ApduInterface
|
import net.typeblog.lpac_jni.ApduInterface
|
||||||
import net.typeblog.lpac_jni.LocalProfileAssistant
|
import net.typeblog.lpac_jni.LocalProfileAssistant
|
||||||
|
@ -13,6 +15,56 @@ interface EuiccChannel {
|
||||||
val logicalSlotId: Int
|
val logicalSlotId: Int
|
||||||
val portId: Int
|
val portId: Int
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A semi-obscure wrapper over the integer ID of a secure element on a card.
|
||||||
|
*
|
||||||
|
* Because the ID is arbitrary, this is intended to discourage the use of the
|
||||||
|
* integer value directly. Additionally, it prevents accidentally calling the
|
||||||
|
* wrong function in EuiccChannelManager with a ton of integer parameters.
|
||||||
|
*/
|
||||||
|
class SecureElementId private constructor(val id: Int) : Parcelable {
|
||||||
|
companion object {
|
||||||
|
val DEFAULT = SecureElementId(0)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a SecureElementId from an integer ID. You should not
|
||||||
|
*/
|
||||||
|
fun createFromInt(id: Int): SecureElementId =
|
||||||
|
SecureElementId(id)
|
||||||
|
|
||||||
|
@Suppress("unused")
|
||||||
|
@JvmField
|
||||||
|
val CREATOR = object : Parcelable.Creator<SecureElementId> {
|
||||||
|
override fun createFromParcel(parcel: Parcel): SecureElementId =
|
||||||
|
createFromInt(parcel.readInt())
|
||||||
|
|
||||||
|
override fun newArray(size: Int): Array<SecureElementId?> = arrayOfNulls(size)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun hashCode(): Int =
|
||||||
|
id.hashCode()
|
||||||
|
|
||||||
|
override fun equals(other: Any?): Boolean =
|
||||||
|
if (other is SecureElementId) {
|
||||||
|
this.id == other.id
|
||||||
|
} else {
|
||||||
|
super.equals(other)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun describeContents(): Int = id
|
||||||
|
|
||||||
|
override fun writeToParcel(parcel: Parcel, flags: Int) {
|
||||||
|
parcel.writeInt(id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Some chips support multiple SEs on one chip. The seId here is intended
|
||||||
|
* to distinguish channels opened from these different SEs.
|
||||||
|
*/
|
||||||
|
val seId: SecureElementId
|
||||||
|
|
||||||
val lpa: LocalProfileAssistant
|
val lpa: LocalProfileAssistant
|
||||||
|
|
||||||
val valid: Boolean
|
val valid: Boolean
|
||||||
|
|
|
@ -6,11 +6,12 @@ import im.angry.openeuicc.util.*
|
||||||
// This class is here instead of inside DI because it contains a bit more logic than just
|
// This class is here instead of inside DI because it contains a bit more logic than just
|
||||||
// "dumb" dependency injection.
|
// "dumb" dependency injection.
|
||||||
interface EuiccChannelFactory {
|
interface EuiccChannelFactory {
|
||||||
suspend fun tryOpenEuiccChannel(port: UiccPortInfoCompat, isdrAid: ByteArray): EuiccChannel?
|
suspend fun tryOpenEuiccChannel(port: UiccPortInfoCompat, isdrAid: ByteArray, seId: EuiccChannel.SecureElementId): EuiccChannel?
|
||||||
|
|
||||||
fun tryOpenUsbEuiccChannel(
|
fun tryOpenUsbEuiccChannel(
|
||||||
ccidCtx: UsbCcidContext,
|
ccidCtx: UsbCcidContext,
|
||||||
isdrAid: ByteArray
|
isdrAid: ByteArray,
|
||||||
|
seId: EuiccChannel.SecureElementId
|
||||||
): EuiccChannel?
|
): EuiccChannel?
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -14,6 +14,7 @@ class EuiccChannelImpl(
|
||||||
override val intrinsicChannelName: String?,
|
override val intrinsicChannelName: String?,
|
||||||
override val apduInterface: ApduInterface,
|
override val apduInterface: ApduInterface,
|
||||||
override val isdrAid: ByteArray,
|
override val isdrAid: ByteArray,
|
||||||
|
override val seId: EuiccChannel.SecureElementId,
|
||||||
verboseLoggingFlow: Flow<Boolean>,
|
verboseLoggingFlow: Flow<Boolean>,
|
||||||
ignoreTLSCertificateFlow: Flow<Boolean>
|
ignoreTLSCertificateFlow: Flow<Boolean>
|
||||||
) : EuiccChannel {
|
) : EuiccChannel {
|
||||||
|
|
|
@ -37,6 +37,14 @@ interface EuiccChannelManager {
|
||||||
*/
|
*/
|
||||||
fun flowAllOpenEuiccPorts(): Flow<Pair<Int, Int>>
|
fun flowAllOpenEuiccPorts(): Flow<Pair<Int, Int>>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Iterate over all the Secure Elements available on one eUICC.
|
||||||
|
*
|
||||||
|
* This is going to almost always return only 1 result, except in the case where
|
||||||
|
* a card has multiple SEs.
|
||||||
|
*/
|
||||||
|
fun flowEuiccSecureElements(slotId: Int, portId: Int): Flow<EuiccChannel.SecureElementId>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Scan all possible USB devices for CCID readers that may contain eUICC cards.
|
* 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
|
* If found, try to open it for access, and add it to the internal EuiccChannel cache
|
||||||
|
@ -81,14 +89,16 @@ interface EuiccChannelManager {
|
||||||
suspend fun <R> withEuiccChannel(
|
suspend fun <R> withEuiccChannel(
|
||||||
physicalSlotId: Int,
|
physicalSlotId: Int,
|
||||||
portId: Int,
|
portId: Int,
|
||||||
|
seId: EuiccChannel.SecureElementId = EuiccChannel.SecureElementId.DEFAULT,
|
||||||
fn: suspend (EuiccChannel) -> R
|
fn: suspend (EuiccChannel) -> R
|
||||||
): R
|
): R
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Same as withEuiccChannel(Int, Int, (EuiccChannel) -> R) but instead uses logical slot ID
|
* Same as withEuiccChannel(Int, Int, SecureElementId, (EuiccChannel) -> R) but instead uses logical slot ID
|
||||||
*/
|
*/
|
||||||
suspend fun <R> withEuiccChannel(
|
suspend fun <R> withEuiccChannel(
|
||||||
logicalSlotId: Int,
|
logicalSlotId: Int,
|
||||||
|
seId: EuiccChannel.SecureElementId = EuiccChannel.SecureElementId.DEFAULT,
|
||||||
fn: suspend (EuiccChannel) -> R
|
fn: suspend (EuiccChannel) -> R
|
||||||
): R
|
): R
|
||||||
|
|
||||||
|
|
|
@ -26,6 +26,8 @@ class EuiccChannelWrapper(orig: EuiccChannel) : EuiccChannel {
|
||||||
get() = channel.logicalSlotId
|
get() = channel.logicalSlotId
|
||||||
override val portId: Int
|
override val portId: Int
|
||||||
get() = channel.portId
|
get() = channel.portId
|
||||||
|
override val seId: EuiccChannel.SecureElementId
|
||||||
|
get() = channel.seId
|
||||||
private val lpaDelegate = lazy {
|
private val lpaDelegate = lazy {
|
||||||
LocalProfileAssistantWrapper(channel.lpa)
|
LocalProfileAssistantWrapper(channel.lpa)
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,13 +2,18 @@ package im.angry.openeuicc.di
|
||||||
|
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.preference.PreferenceFragmentCompat
|
import androidx.preference.PreferenceFragmentCompat
|
||||||
|
import im.angry.openeuicc.core.EuiccChannel
|
||||||
import im.angry.openeuicc.ui.EuiccManagementFragment
|
import im.angry.openeuicc.ui.EuiccManagementFragment
|
||||||
import im.angry.openeuicc.ui.NoEuiccPlaceholderFragment
|
import im.angry.openeuicc.ui.NoEuiccPlaceholderFragment
|
||||||
import im.angry.openeuicc.ui.SettingsFragment
|
import im.angry.openeuicc.ui.SettingsFragment
|
||||||
|
|
||||||
open class DefaultUiComponentFactory : UiComponentFactory {
|
open class DefaultUiComponentFactory : UiComponentFactory {
|
||||||
override fun createEuiccManagementFragment(slotId: Int, portId: Int): EuiccManagementFragment =
|
override fun createEuiccManagementFragment(
|
||||||
EuiccManagementFragment.newInstance(slotId, portId)
|
slotId: Int,
|
||||||
|
portId: Int,
|
||||||
|
seId: EuiccChannel.SecureElementId
|
||||||
|
): EuiccManagementFragment =
|
||||||
|
EuiccManagementFragment.newInstance(slotId, portId, seId)
|
||||||
|
|
||||||
override fun createNoEuiccPlaceholderFragment(): Fragment = NoEuiccPlaceholderFragment()
|
override fun createNoEuiccPlaceholderFragment(): Fragment = NoEuiccPlaceholderFragment()
|
||||||
|
|
||||||
|
|
|
@ -2,10 +2,16 @@ package im.angry.openeuicc.di
|
||||||
|
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.preference.PreferenceFragmentCompat
|
import androidx.preference.PreferenceFragmentCompat
|
||||||
|
import im.angry.openeuicc.core.EuiccChannel
|
||||||
import im.angry.openeuicc.ui.EuiccManagementFragment
|
import im.angry.openeuicc.ui.EuiccManagementFragment
|
||||||
|
|
||||||
interface UiComponentFactory {
|
interface UiComponentFactory {
|
||||||
fun createEuiccManagementFragment(slotId: Int, portId: Int): EuiccManagementFragment
|
fun createEuiccManagementFragment(
|
||||||
|
slotId: Int,
|
||||||
|
portId: Int,
|
||||||
|
seId: EuiccChannel.SecureElementId
|
||||||
|
): EuiccManagementFragment
|
||||||
|
|
||||||
fun createNoEuiccPlaceholderFragment(): Fragment
|
fun createNoEuiccPlaceholderFragment(): Fragment
|
||||||
fun createSettingsFragment(): Fragment
|
fun createSettingsFragment(): Fragment
|
||||||
}
|
}
|
|
@ -43,6 +43,7 @@ class EuiccInfoActivity : BaseEuiccAccessActivity(), OpenEuiccContextMarker {
|
||||||
private lateinit var infoList: RecyclerView
|
private lateinit var infoList: RecyclerView
|
||||||
|
|
||||||
private var logicalSlotId: Int = -1
|
private var logicalSlotId: Int = -1
|
||||||
|
private var seId: EuiccChannel.SecureElementId = EuiccChannel.SecureElementId.DEFAULT
|
||||||
|
|
||||||
data class Item(
|
data class Item(
|
||||||
@StringRes
|
@StringRes
|
||||||
|
@ -67,6 +68,12 @@ class EuiccInfoActivity : BaseEuiccAccessActivity(), OpenEuiccContextMarker {
|
||||||
}
|
}
|
||||||
|
|
||||||
logicalSlotId = intent.getIntExtra("logicalSlotId", 0)
|
logicalSlotId = intent.getIntExtra("logicalSlotId", 0)
|
||||||
|
seId = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||||
|
intent.getParcelableExtra("seId", EuiccChannel.SecureElementId::class.java)
|
||||||
|
} else {
|
||||||
|
@Suppress("DEPRECATION")
|
||||||
|
intent.getParcelableExtra("seId")!!
|
||||||
|
} ?: EuiccChannel.SecureElementId.DEFAULT
|
||||||
|
|
||||||
val channelTitle = if (logicalSlotId == EuiccChannelManager.USB_CHANNEL_ID) {
|
val channelTitle = if (logicalSlotId == EuiccChannelManager.USB_CHANNEL_ID) {
|
||||||
getString(R.string.usb)
|
getString(R.string.usb)
|
||||||
|
@ -99,7 +106,7 @@ class EuiccInfoActivity : BaseEuiccAccessActivity(), OpenEuiccContextMarker {
|
||||||
|
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
(infoList.adapter!! as EuiccInfoAdapter).euiccInfoItems =
|
(infoList.adapter!! as EuiccInfoAdapter).euiccInfoItems =
|
||||||
euiccChannelManager.withEuiccChannel(logicalSlotId, ::buildEuiccInfoItems)
|
euiccChannelManager.withEuiccChannel(logicalSlotId, fn = ::buildEuiccInfoItems)
|
||||||
|
|
||||||
swipeRefresh.isRefreshing = false
|
swipeRefresh.isRefreshing = false
|
||||||
}
|
}
|
||||||
|
@ -107,12 +114,31 @@ class EuiccInfoActivity : BaseEuiccAccessActivity(), OpenEuiccContextMarker {
|
||||||
|
|
||||||
private fun buildEuiccInfoItems(channel: EuiccChannel) = buildList {
|
private fun buildEuiccInfoItems(channel: EuiccChannel) = buildList {
|
||||||
add(Item(R.string.euicc_info_access_mode, channel.type))
|
add(Item(R.string.euicc_info_access_mode, channel.type))
|
||||||
add(Item(R.string.euicc_info_removable, formatByBoolean(channel.port.card.isRemovable, YES_NO)))
|
add(
|
||||||
add(Item(R.string.euicc_info_eid, channel.lpa.eID, copiedToastResId = R.string.toast_eid_copied))
|
Item(
|
||||||
|
R.string.euicc_info_removable,
|
||||||
|
formatByBoolean(channel.port.card.isRemovable, YES_NO)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
add(
|
||||||
|
Item(
|
||||||
|
R.string.euicc_info_eid,
|
||||||
|
channel.lpa.eID,
|
||||||
|
copiedToastResId = R.string.toast_eid_copied
|
||||||
|
)
|
||||||
|
)
|
||||||
add(Item(R.string.euicc_info_isdr_aid, channel.isdrAid.encodeHex()))
|
add(Item(R.string.euicc_info_isdr_aid, channel.isdrAid.encodeHex()))
|
||||||
channel.tryParseEuiccVendorInfo()?.let { vendorInfo ->
|
channel.tryParseEuiccVendorInfo()?.let { vendorInfo ->
|
||||||
vendorInfo.skuName?.let { add(Item(R.string.euicc_info_sku, it)) }
|
vendorInfo.skuName?.let { add(Item(R.string.euicc_info_sku, it)) }
|
||||||
vendorInfo.serialNumber?.let { add(Item(R.string.euicc_info_sn, it, copiedToastResId = R.string.toast_sn_copied)) }
|
vendorInfo.serialNumber?.let {
|
||||||
|
add(
|
||||||
|
Item(
|
||||||
|
R.string.euicc_info_sn,
|
||||||
|
it,
|
||||||
|
copiedToastResId = R.string.toast_sn_copied
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
vendorInfo.firmwareVersion?.let { add(Item(R.string.euicc_info_fw_ver, it)) }
|
vendorInfo.firmwareVersion?.let { add(Item(R.string.euicc_info_fw_ver, it)) }
|
||||||
vendorInfo.bootloaderVersion?.let { add(Item(R.string.euicc_info_bl_ver, it)) }
|
vendorInfo.bootloaderVersion?.let { add(Item(R.string.euicc_info_bl_ver, it)) }
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,6 +31,7 @@ import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
||||||
import com.google.android.material.floatingactionbutton.FloatingActionButton
|
import com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||||
import net.typeblog.lpac_jni.LocalProfileInfo
|
import net.typeblog.lpac_jni.LocalProfileInfo
|
||||||
import im.angry.openeuicc.common.R
|
import im.angry.openeuicc.common.R
|
||||||
|
import im.angry.openeuicc.core.EuiccChannel
|
||||||
import im.angry.openeuicc.service.EuiccChannelManagerService
|
import im.angry.openeuicc.service.EuiccChannelManagerService
|
||||||
import im.angry.openeuicc.service.EuiccChannelManagerService.Companion.waitDone
|
import im.angry.openeuicc.service.EuiccChannelManagerService.Companion.waitDone
|
||||||
import im.angry.openeuicc.ui.wizard.DownloadWizardActivity
|
import im.angry.openeuicc.ui.wizard.DownloadWizardActivity
|
||||||
|
@ -49,8 +50,12 @@ open class EuiccManagementFragment : Fragment(), EuiccProfilesChangedListener,
|
||||||
companion object {
|
companion object {
|
||||||
const val TAG = "EuiccManagementFragment"
|
const val TAG = "EuiccManagementFragment"
|
||||||
|
|
||||||
fun newInstance(slotId: Int, portId: Int): EuiccManagementFragment =
|
fun newInstance(
|
||||||
newInstanceEuicc(EuiccManagementFragment::class.java, slotId, portId)
|
slotId: Int,
|
||||||
|
portId: Int,
|
||||||
|
seId: EuiccChannel.SecureElementId
|
||||||
|
): EuiccManagementFragment =
|
||||||
|
newInstanceEuicc(EuiccManagementFragment::class.java, slotId, portId, seId)
|
||||||
}
|
}
|
||||||
|
|
||||||
private lateinit var swipeRefresh: SwipeRefreshLayout
|
private lateinit var swipeRefresh: SwipeRefreshLayout
|
||||||
|
@ -148,6 +153,7 @@ open class EuiccManagementFragment : Fragment(), EuiccProfilesChangedListener,
|
||||||
R.id.show_notifications -> {
|
R.id.show_notifications -> {
|
||||||
Intent(requireContext(), NotificationsActivity::class.java).apply {
|
Intent(requireContext(), NotificationsActivity::class.java).apply {
|
||||||
putExtra("logicalSlotId", logicalSlotId)
|
putExtra("logicalSlotId", logicalSlotId)
|
||||||
|
putExtra("seId", seId)
|
||||||
startActivity(this)
|
startActivity(this)
|
||||||
}
|
}
|
||||||
true
|
true
|
||||||
|
@ -156,13 +162,14 @@ open class EuiccManagementFragment : Fragment(), EuiccProfilesChangedListener,
|
||||||
R.id.euicc_info -> {
|
R.id.euicc_info -> {
|
||||||
Intent(requireContext(), EuiccInfoActivity::class.java).apply {
|
Intent(requireContext(), EuiccInfoActivity::class.java).apply {
|
||||||
putExtra("logicalSlotId", logicalSlotId)
|
putExtra("logicalSlotId", logicalSlotId)
|
||||||
|
putExtra("seId", seId)
|
||||||
startActivity(this)
|
startActivity(this)
|
||||||
}
|
}
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
R.id.euicc_memory_reset -> {
|
R.id.euicc_memory_reset -> {
|
||||||
EuiccMemoryResetFragment.newInstance(slotId, portId, eid)
|
EuiccMemoryResetFragment.newInstance(slotId, portId, seId, eid)
|
||||||
.show(childFragmentManager, EuiccMemoryResetFragment.TAG)
|
.show(childFragmentManager, EuiccMemoryResetFragment.TAG)
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
@ -294,7 +301,10 @@ open class EuiccManagementFragment : Fragment(), EuiccProfilesChangedListener,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected open fun populatePopupWithProfileActions(popup: PopupMenu, profile: LocalProfileInfo) {
|
protected open fun populatePopupWithProfileActions(
|
||||||
|
popup: PopupMenu,
|
||||||
|
profile: LocalProfileInfo
|
||||||
|
) {
|
||||||
popup.inflate(R.menu.profile_options)
|
popup.inflate(R.menu.profile_options)
|
||||||
if (profile.isEnabled) {
|
if (profile.isEnabled) {
|
||||||
popup.menu.findItem(R.id.enable).isVisible = false
|
popup.menu.findItem(R.id.enable).isVisible = false
|
||||||
|
@ -321,7 +331,7 @@ open class EuiccManagementFragment : Fragment(), EuiccProfilesChangedListener,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
inner class FooterViewHolder: ViewHolder(FrameLayout(requireContext())) {
|
inner class FooterViewHolder : ViewHolder(FrameLayout(requireContext())) {
|
||||||
init {
|
init {
|
||||||
itemView.layoutParams = ViewGroup.LayoutParams(
|
itemView.layoutParams = ViewGroup.LayoutParams(
|
||||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||||
|
@ -413,20 +423,36 @@ open class EuiccManagementFragment : Fragment(), EuiccProfilesChangedListener,
|
||||||
enableOrDisableProfile(profile.iccid, true)
|
enableOrDisableProfile(profile.iccid, true)
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
R.id.disable -> {
|
R.id.disable -> {
|
||||||
enableOrDisableProfile(profile.iccid, false)
|
enableOrDisableProfile(profile.iccid, false)
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
R.id.rename -> {
|
R.id.rename -> {
|
||||||
ProfileRenameFragment.newInstance(slotId, portId, profile.iccid, profile.displayName)
|
ProfileRenameFragment.newInstance(
|
||||||
|
slotId,
|
||||||
|
portId,
|
||||||
|
seId,
|
||||||
|
profile.iccid,
|
||||||
|
profile.displayName
|
||||||
|
)
|
||||||
.show(childFragmentManager, ProfileRenameFragment.TAG)
|
.show(childFragmentManager, ProfileRenameFragment.TAG)
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
R.id.delete -> {
|
R.id.delete -> {
|
||||||
ProfileDeleteFragment.newInstance(slotId, portId, profile.iccid, profile.displayName)
|
ProfileDeleteFragment.newInstance(
|
||||||
|
slotId,
|
||||||
|
portId,
|
||||||
|
seId,
|
||||||
|
profile.iccid,
|
||||||
|
profile.displayName
|
||||||
|
)
|
||||||
.show(childFragmentManager, ProfileDeleteFragment.TAG)
|
.show(childFragmentManager, ProfileDeleteFragment.TAG)
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> false
|
else -> false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -438,9 +464,11 @@ open class EuiccManagementFragment : Fragment(), EuiccProfilesChangedListener,
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder =
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder =
|
||||||
when (ViewHolder.Type.fromInt(viewType)) {
|
when (ViewHolder.Type.fromInt(viewType)) {
|
||||||
ViewHolder.Type.PROFILE -> {
|
ViewHolder.Type.PROFILE -> {
|
||||||
val view = LayoutInflater.from(parent.context).inflate(R.layout.euicc_profile, parent, false)
|
val view = LayoutInflater.from(parent.context)
|
||||||
|
.inflate(R.layout.euicc_profile, parent, false)
|
||||||
ProfileViewHolder(view)
|
ProfileViewHolder(view)
|
||||||
}
|
}
|
||||||
|
|
||||||
ViewHolder.Type.FOOTER -> {
|
ViewHolder.Type.FOOTER -> {
|
||||||
FooterViewHolder()
|
FooterViewHolder()
|
||||||
}
|
}
|
||||||
|
@ -451,9 +479,11 @@ open class EuiccManagementFragment : Fragment(), EuiccProfilesChangedListener,
|
||||||
position < profiles.size -> {
|
position < profiles.size -> {
|
||||||
ViewHolder.Type.PROFILE.value
|
ViewHolder.Type.PROFILE.value
|
||||||
}
|
}
|
||||||
|
|
||||||
position >= profiles.size && position < profiles.size + footerViews.size -> {
|
position >= profiles.size && position < profiles.size + footerViews.size -> {
|
||||||
ViewHolder.Type.FOOTER.value
|
ViewHolder.Type.FOOTER.value
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> -1
|
else -> -1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -462,6 +492,7 @@ open class EuiccManagementFragment : Fragment(), EuiccProfilesChangedListener,
|
||||||
is ProfileViewHolder -> {
|
is ProfileViewHolder -> {
|
||||||
holder.setProfile(profiles[position])
|
holder.setProfile(profiles[position])
|
||||||
}
|
}
|
||||||
|
|
||||||
is FooterViewHolder -> {
|
is FooterViewHolder -> {
|
||||||
holder.attach(footerViews[position - profiles.size])
|
holder.attach(footerViews[position - profiles.size])
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,7 @@ import androidx.fragment.app.DialogFragment
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import im.angry.openeuicc.common.R
|
import im.angry.openeuicc.common.R
|
||||||
|
import im.angry.openeuicc.core.EuiccChannel
|
||||||
import im.angry.openeuicc.service.EuiccChannelManagerService.Companion.waitDone
|
import im.angry.openeuicc.service.EuiccChannelManagerService.Companion.waitDone
|
||||||
import im.angry.openeuicc.util.EuiccChannelFragmentMarker
|
import im.angry.openeuicc.util.EuiccChannelFragmentMarker
|
||||||
import im.angry.openeuicc.util.EuiccProfilesChangedListener
|
import im.angry.openeuicc.util.EuiccProfilesChangedListener
|
||||||
|
@ -29,8 +30,8 @@ class EuiccMemoryResetFragment : DialogFragment(), EuiccChannelFragmentMarker {
|
||||||
|
|
||||||
private const val FIELD_EID = "eid"
|
private const val FIELD_EID = "eid"
|
||||||
|
|
||||||
fun newInstance(slotId: Int, portId: Int, eid: String) =
|
fun newInstance(slotId: Int, portId: Int, seId: EuiccChannel.SecureElementId, eid: String) =
|
||||||
newInstanceEuicc(EuiccMemoryResetFragment::class.java, slotId, portId) {
|
newInstanceEuicc(EuiccMemoryResetFragment::class.java, slotId, portId, seId) {
|
||||||
putString(FIELD_EID, eid)
|
putString(FIELD_EID, eid)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,6 +23,7 @@ import androidx.viewpager2.widget.ViewPager2
|
||||||
import com.google.android.material.tabs.TabLayout
|
import com.google.android.material.tabs.TabLayout
|
||||||
import com.google.android.material.tabs.TabLayoutMediator
|
import com.google.android.material.tabs.TabLayoutMediator
|
||||||
import im.angry.openeuicc.common.R
|
import im.angry.openeuicc.common.R
|
||||||
|
import im.angry.openeuicc.core.EuiccChannel
|
||||||
import im.angry.openeuicc.core.EuiccChannelManager
|
import im.angry.openeuicc.core.EuiccChannelManager
|
||||||
import im.angry.openeuicc.util.*
|
import im.angry.openeuicc.util.*
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
@ -112,10 +113,12 @@ open class MainActivity : BaseEuiccAccessActivity(), OpenEuiccContextMarker {
|
||||||
startActivity(Intent(this, SettingsActivity::class.java))
|
startActivity(Intent(this, SettingsActivity::class.java))
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
R.id.reload -> {
|
R.id.reload -> {
|
||||||
refresh()
|
refresh()
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> super.onOptionsItemSelected(item)
|
else -> super.onOptionsItemSelected(item)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -154,21 +157,27 @@ open class MainActivity : BaseEuiccAccessActivity(), OpenEuiccContextMarker {
|
||||||
euiccChannelManager.flowInternalEuiccPorts().onEach { (slotId, portId) ->
|
euiccChannelManager.flowInternalEuiccPorts().onEach { (slotId, portId) ->
|
||||||
Log.d(TAG, "slot $slotId port $portId")
|
Log.d(TAG, "slot $slotId port $portId")
|
||||||
|
|
||||||
euiccChannelManager.withEuiccChannel(slotId, portId) { channel ->
|
euiccChannelManager.flowEuiccSecureElements(slotId, portId).onEach { seId ->
|
||||||
if (preferenceRepository.verboseLoggingFlow.first()) {
|
euiccChannelManager.withEuiccChannel(slotId, portId, seId) { channel ->
|
||||||
Log.d(TAG, channel.lpa.eID)
|
if (preferenceRepository.verboseLoggingFlow.first()) {
|
||||||
}
|
Log.d(TAG, channel.lpa.eID)
|
||||||
// Request the system to refresh the list of profiles every time we start
|
}
|
||||||
// Note that this is currently supposed to be no-op when unprivileged,
|
// Request the system to refresh the list of profiles every time we start
|
||||||
// but it could change in the future
|
// Note that this is currently supposed to be no-op when unprivileged,
|
||||||
euiccChannelManager.notifyEuiccProfilesChanged(channel.logicalSlotId)
|
// but it could change in the future
|
||||||
|
euiccChannelManager.notifyEuiccProfilesChanged(channel.logicalSlotId)
|
||||||
|
|
||||||
val channelName =
|
val channelName =
|
||||||
appContainer.customizableTextProvider.formatInternalChannelName(channel.logicalSlotId)
|
appContainer.customizableTextProvider.formatInternalChannelName(channel.logicalSlotId)
|
||||||
newPages.add(Page(channel.logicalSlotId, channelName) {
|
newPages.add(Page(channel.logicalSlotId, channelName) {
|
||||||
appContainer.uiComponentFactory.createEuiccManagementFragment(slotId, portId)
|
appContainer.uiComponentFactory.createEuiccManagementFragment(
|
||||||
})
|
slotId,
|
||||||
}
|
portId,
|
||||||
|
seId
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}.collect()
|
||||||
}.collect()
|
}.collect()
|
||||||
|
|
||||||
// If USB readers exist, add them at the very last
|
// If USB readers exist, add them at the very last
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package im.angry.openeuicc.ui
|
package im.angry.openeuicc.ui
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.text.Html
|
import android.text.Html
|
||||||
import android.view.ContextMenu
|
import android.view.ContextMenu
|
||||||
|
@ -20,6 +21,7 @@ import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
||||||
import im.angry.openeuicc.common.R
|
import im.angry.openeuicc.common.R
|
||||||
|
import im.angry.openeuicc.core.EuiccChannel
|
||||||
import im.angry.openeuicc.core.EuiccChannelManager
|
import im.angry.openeuicc.core.EuiccChannelManager
|
||||||
import im.angry.openeuicc.util.*
|
import im.angry.openeuicc.util.*
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
@ -27,12 +29,13 @@ import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import net.typeblog.lpac_jni.LocalProfileNotification
|
import net.typeblog.lpac_jni.LocalProfileNotification
|
||||||
|
|
||||||
class NotificationsActivity: BaseEuiccAccessActivity(), OpenEuiccContextMarker {
|
class NotificationsActivity : BaseEuiccAccessActivity(), OpenEuiccContextMarker {
|
||||||
private lateinit var swipeRefresh: SwipeRefreshLayout
|
private lateinit var swipeRefresh: SwipeRefreshLayout
|
||||||
private lateinit var notificationList: RecyclerView
|
private lateinit var notificationList: RecyclerView
|
||||||
private val notificationAdapter = NotificationAdapter()
|
private val notificationAdapter = NotificationAdapter()
|
||||||
|
|
||||||
private var logicalSlotId = -1
|
private var logicalSlotId = -1
|
||||||
|
private var seId = EuiccChannel.SecureElementId.DEFAULT
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
enableEdgeToEdge()
|
enableEdgeToEdge()
|
||||||
|
@ -51,11 +54,22 @@ class NotificationsActivity: BaseEuiccAccessActivity(), OpenEuiccContextMarker {
|
||||||
override fun onInit() {
|
override fun onInit() {
|
||||||
notificationList.layoutManager =
|
notificationList.layoutManager =
|
||||||
LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false)
|
LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false)
|
||||||
notificationList.addItemDecoration(DividerItemDecoration(this, LinearLayoutManager.VERTICAL))
|
notificationList.addItemDecoration(
|
||||||
|
DividerItemDecoration(
|
||||||
|
this,
|
||||||
|
LinearLayoutManager.VERTICAL
|
||||||
|
)
|
||||||
|
)
|
||||||
notificationList.adapter = notificationAdapter
|
notificationList.adapter = notificationAdapter
|
||||||
registerForContextMenu(notificationList)
|
registerForContextMenu(notificationList)
|
||||||
|
|
||||||
logicalSlotId = intent.getIntExtra("logicalSlotId", 0)
|
logicalSlotId = intent.getIntExtra("logicalSlotId", 0)
|
||||||
|
seId = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||||
|
intent.getParcelableExtra("seId", EuiccChannel.SecureElementId::class.java)
|
||||||
|
} else {
|
||||||
|
@Suppress("DEPRECATION")
|
||||||
|
intent.getParcelableExtra("seId")!!
|
||||||
|
} ?: EuiccChannel.SecureElementId.DEFAULT
|
||||||
|
|
||||||
// This is slightly different from the MainActivity logic
|
// This is slightly different from the MainActivity logic
|
||||||
// due to the length (we don't want to display the full USB product name)
|
// due to the length (we don't want to display the full USB product name)
|
||||||
|
@ -86,6 +100,7 @@ class NotificationsActivity: BaseEuiccAccessActivity(), OpenEuiccContextMarker {
|
||||||
finish()
|
finish()
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
R.id.help -> {
|
R.id.help -> {
|
||||||
AlertDialog.Builder(this, R.style.AlertDialogTheme).apply {
|
AlertDialog.Builder(this, R.style.AlertDialogTheme).apply {
|
||||||
setMessage(R.string.profile_notifications_help)
|
setMessage(R.string.profile_notifications_help)
|
||||||
|
@ -96,6 +111,7 @@ class NotificationsActivity: BaseEuiccAccessActivity(), OpenEuiccContextMarker {
|
||||||
}
|
}
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> super.onOptionsItemSelected(item)
|
else -> super.onOptionsItemSelected(item)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -114,20 +130,20 @@ class NotificationsActivity: BaseEuiccAccessActivity(), OpenEuiccContextMarker {
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun refresh() {
|
private fun refresh() {
|
||||||
launchTask {
|
launchTask {
|
||||||
notificationAdapter.notifications =
|
notificationAdapter.notifications =
|
||||||
euiccChannelManager.withEuiccChannel(logicalSlotId) { channel ->
|
euiccChannelManager.withEuiccChannel(logicalSlotId) { channel ->
|
||||||
val nameMap = buildMap {
|
val nameMap = buildMap {
|
||||||
for (profile in channel.lpa.profiles) {
|
for (profile in channel.lpa.profiles) {
|
||||||
put(profile.iccid, profile.displayName)
|
put(profile.iccid, profile.displayName)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
channel.lpa.notifications.map {
|
channel.lpa.notifications.map {
|
||||||
LocalProfileNotificationWrapper(it, nameMap[it.iccid] ?: "???")
|
LocalProfileNotificationWrapper(it, nameMap[it.iccid] ?: "???")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
data class LocalProfileNotificationWrapper(
|
data class LocalProfileNotificationWrapper(
|
||||||
|
@ -136,7 +152,7 @@ class NotificationsActivity: BaseEuiccAccessActivity(), OpenEuiccContextMarker {
|
||||||
)
|
)
|
||||||
|
|
||||||
@SuppressLint("ClickableViewAccessibility")
|
@SuppressLint("ClickableViewAccessibility")
|
||||||
inner class NotificationViewHolder(private val root: View):
|
inner class NotificationViewHolder(private val root: View) :
|
||||||
RecyclerView.ViewHolder(root), View.OnCreateContextMenuListener, OnMenuItemClickListener {
|
RecyclerView.ViewHolder(root), View.OnCreateContextMenuListener, OnMenuItemClickListener {
|
||||||
private val address: TextView = root.requireViewById(R.id.notification_address)
|
private val address: TextView = root.requireViewById(R.id.notification_address)
|
||||||
private val sequenceNumber: TextView =
|
private val sequenceNumber: TextView =
|
||||||
|
@ -170,7 +186,8 @@ class NotificationsActivity: BaseEuiccAccessActivity(), OpenEuiccContextMarker {
|
||||||
LocalProfileNotification.Operation.Delete -> R.string.profile_notification_operation_delete
|
LocalProfileNotification.Operation.Delete -> R.string.profile_notification_operation_delete
|
||||||
LocalProfileNotification.Operation.Enable -> R.string.profile_notification_operation_enable
|
LocalProfileNotification.Operation.Enable -> R.string.profile_notification_operation_enable
|
||||||
LocalProfileNotification.Operation.Disable -> R.string.profile_notification_operation_disable
|
LocalProfileNotification.Operation.Disable -> R.string.profile_notification_operation_disable
|
||||||
})
|
}
|
||||||
|
)
|
||||||
|
|
||||||
fun updateNotification(value: LocalProfileNotificationWrapper) {
|
fun updateNotification(value: LocalProfileNotificationWrapper) {
|
||||||
notification = value
|
notification = value
|
||||||
|
@ -181,10 +198,13 @@ class NotificationsActivity: BaseEuiccAccessActivity(), OpenEuiccContextMarker {
|
||||||
value.inner.seqNumber
|
value.inner.seqNumber
|
||||||
)
|
)
|
||||||
profileName.text = Html.fromHtml(
|
profileName.text = Html.fromHtml(
|
||||||
root.context.getString(R.string.profile_notification_name_format,
|
root.context.getString(
|
||||||
|
R.string.profile_notification_name_format,
|
||||||
operationToLocalizedText(value.inner.profileManagementOperation),
|
operationToLocalizedText(value.inner.profileManagementOperation),
|
||||||
value.profileName, value.inner.iccid),
|
value.profileName, value.inner.iccid
|
||||||
Html.FROM_HTML_MODE_COMPACT)
|
),
|
||||||
|
Html.FROM_HTML_MODE_COMPACT
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreateContextMenu(
|
override fun onCreateContextMenu(
|
||||||
|
@ -213,6 +233,7 @@ class NotificationsActivity: BaseEuiccAccessActivity(), OpenEuiccContextMarker {
|
||||||
}
|
}
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
R.id.notification_delete -> {
|
R.id.notification_delete -> {
|
||||||
launchTask {
|
launchTask {
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
|
@ -225,11 +246,12 @@ class NotificationsActivity: BaseEuiccAccessActivity(), OpenEuiccContextMarker {
|
||||||
}
|
}
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> false
|
else -> false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
inner class NotificationAdapter: RecyclerView.Adapter<NotificationViewHolder>() {
|
inner class NotificationAdapter : RecyclerView.Adapter<NotificationViewHolder>() {
|
||||||
var notifications: List<LocalProfileNotificationWrapper> = listOf()
|
var notifications: List<LocalProfileNotificationWrapper> = listOf()
|
||||||
@SuppressLint("NotifyDataSetChanged")
|
@SuppressLint("NotifyDataSetChanged")
|
||||||
set(value) {
|
set(value) {
|
||||||
|
|
|
@ -9,6 +9,7 @@ import androidx.appcompat.app.AlertDialog
|
||||||
import androidx.fragment.app.DialogFragment
|
import androidx.fragment.app.DialogFragment
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import im.angry.openeuicc.common.R
|
import im.angry.openeuicc.common.R
|
||||||
|
import im.angry.openeuicc.core.EuiccChannel
|
||||||
import im.angry.openeuicc.service.EuiccChannelManagerService.Companion.waitDone
|
import im.angry.openeuicc.service.EuiccChannelManagerService.Companion.waitDone
|
||||||
import im.angry.openeuicc.util.*
|
import im.angry.openeuicc.util.*
|
||||||
import kotlinx.coroutines.flow.onStart
|
import kotlinx.coroutines.flow.onStart
|
||||||
|
@ -20,8 +21,8 @@ class ProfileDeleteFragment : DialogFragment(), EuiccChannelFragmentMarker {
|
||||||
private const val FIELD_ICCID = "iccid"
|
private const val FIELD_ICCID = "iccid"
|
||||||
private const val FIELD_NAME = "name"
|
private const val FIELD_NAME = "name"
|
||||||
|
|
||||||
fun newInstance(slotId: Int, portId: Int, iccid: String, name: String) =
|
fun newInstance(slotId: Int, portId: Int, seId: EuiccChannel.SecureElementId, iccid: String, name: String) =
|
||||||
newInstanceEuicc(ProfileDeleteFragment::class.java, slotId, portId) {
|
newInstanceEuicc(ProfileDeleteFragment::class.java, slotId, portId, seId) {
|
||||||
putString(FIELD_ICCID, iccid)
|
putString(FIELD_ICCID, iccid)
|
||||||
putString(FIELD_NAME, name)
|
putString(FIELD_NAME, name)
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,6 +12,7 @@ import androidx.appcompat.widget.Toolbar
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import com.google.android.material.textfield.TextInputLayout
|
import com.google.android.material.textfield.TextInputLayout
|
||||||
import im.angry.openeuicc.common.R
|
import im.angry.openeuicc.common.R
|
||||||
|
import im.angry.openeuicc.core.EuiccChannel
|
||||||
import im.angry.openeuicc.service.EuiccChannelManagerService.Companion.waitDone
|
import im.angry.openeuicc.service.EuiccChannelManagerService.Companion.waitDone
|
||||||
import im.angry.openeuicc.util.*
|
import im.angry.openeuicc.util.*
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
@ -24,8 +25,8 @@ class ProfileRenameFragment : BaseMaterialDialogFragment(), EuiccChannelFragment
|
||||||
|
|
||||||
const val TAG = "ProfileRenameFragment"
|
const val TAG = "ProfileRenameFragment"
|
||||||
|
|
||||||
fun newInstance(slotId: Int, portId: Int, iccid: String, currentName: String) =
|
fun newInstance(slotId: Int, portId: Int, seId: EuiccChannel.SecureElementId, iccid: String, currentName: String) =
|
||||||
newInstanceEuicc(ProfileRenameFragment::class.java, slotId, portId) {
|
newInstanceEuicc(ProfileRenameFragment::class.java, slotId, portId, seId) {
|
||||||
putString(FIELD_ICCID, iccid)
|
putString(FIELD_ICCID, iccid)
|
||||||
putString(FIELD_CURRENT_NAME, currentName)
|
putString(FIELD_CURRENT_NAME, currentName)
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,6 +20,7 @@ import androidx.fragment.app.Fragment
|
||||||
import androidx.fragment.app.commit
|
import androidx.fragment.app.commit
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import im.angry.openeuicc.common.R
|
import im.angry.openeuicc.common.R
|
||||||
|
import im.angry.openeuicc.core.EuiccChannel
|
||||||
import im.angry.openeuicc.core.EuiccChannelManager
|
import im.angry.openeuicc.core.EuiccChannelManager
|
||||||
import im.angry.openeuicc.util.*
|
import im.angry.openeuicc.util.*
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
@ -156,7 +157,9 @@ class UsbCcidReaderFragment : Fragment(), OpenEuiccContextMarker {
|
||||||
R.id.child_container,
|
R.id.child_container,
|
||||||
appContainer.uiComponentFactory.createEuiccManagementFragment(
|
appContainer.uiComponentFactory.createEuiccManagementFragment(
|
||||||
slotId = EuiccChannelManager.USB_CHANNEL_ID,
|
slotId = EuiccChannelManager.USB_CHANNEL_ID,
|
||||||
portId = 0
|
portId = 0,
|
||||||
|
// TODO: What if a USB card has multiple SEs?
|
||||||
|
seId = EuiccChannel.SecureElementId.DEFAULT
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package im.angry.openeuicc.util
|
package im.angry.openeuicc.util
|
||||||
|
|
||||||
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import im.angry.openeuicc.core.EuiccChannel
|
import im.angry.openeuicc.core.EuiccChannel
|
||||||
|
@ -9,6 +10,7 @@ import im.angry.openeuicc.ui.BaseEuiccAccessActivity
|
||||||
|
|
||||||
private const val FIELD_SLOT_ID = "slotId"
|
private const val FIELD_SLOT_ID = "slotId"
|
||||||
private const val FIELD_PORT_ID = "portId"
|
private const val FIELD_PORT_ID = "portId"
|
||||||
|
private const val FIELD_SE_ID = "seId"
|
||||||
|
|
||||||
interface EuiccChannelFragmentMarker : OpenEuiccContextMarker
|
interface EuiccChannelFragmentMarker : OpenEuiccContextMarker
|
||||||
|
|
||||||
|
@ -17,12 +19,19 @@ private typealias BundleSetter = Bundle.() -> Unit
|
||||||
// We must use extension functions because there is no way to add bounds to the type of "self"
|
// We must use extension functions because there is no way to add bounds to the type of "self"
|
||||||
// in the definition of an interface, so the only way is to limit where the extension functions
|
// in the definition of an interface, so the only way is to limit where the extension functions
|
||||||
// can be applied.
|
// can be applied.
|
||||||
fun <T> newInstanceEuicc(clazz: Class<T>, slotId: Int, portId: Int, addArguments: BundleSetter = {}): T
|
fun <T> newInstanceEuicc(
|
||||||
|
clazz: Class<T>,
|
||||||
|
slotId: Int,
|
||||||
|
portId: Int,
|
||||||
|
seId: EuiccChannel.SecureElementId,
|
||||||
|
addArguments: BundleSetter = {}
|
||||||
|
): T
|
||||||
where T : Fragment, T : EuiccChannelFragmentMarker =
|
where T : Fragment, T : EuiccChannelFragmentMarker =
|
||||||
clazz.getDeclaredConstructor().newInstance().apply {
|
clazz.getDeclaredConstructor().newInstance().apply {
|
||||||
arguments = Bundle()
|
arguments = Bundle()
|
||||||
arguments!!.putInt(FIELD_SLOT_ID, slotId)
|
arguments!!.putInt(FIELD_SLOT_ID, slotId)
|
||||||
arguments!!.putInt(FIELD_PORT_ID, portId)
|
arguments!!.putInt(FIELD_PORT_ID, portId)
|
||||||
|
arguments!!.putParcelable(FIELD_SE_ID, seId)
|
||||||
arguments!!.addArguments()
|
arguments!!.addArguments()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -35,6 +44,18 @@ val <T> T.slotId: Int
|
||||||
val <T> T.portId: Int
|
val <T> T.portId: Int
|
||||||
where T : Fragment, T : EuiccChannelFragmentMarker
|
where T : Fragment, T : EuiccChannelFragmentMarker
|
||||||
get() = requireArguments().getInt(FIELD_PORT_ID)
|
get() = requireArguments().getInt(FIELD_PORT_ID)
|
||||||
|
val <T> T.seId: EuiccChannel.SecureElementId
|
||||||
|
where T : Fragment, T : EuiccChannelFragmentMarker
|
||||||
|
get() =
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||||
|
requireArguments().getParcelable(
|
||||||
|
FIELD_SE_ID,
|
||||||
|
EuiccChannel.SecureElementId::class.java
|
||||||
|
)!!
|
||||||
|
} else {
|
||||||
|
@Suppress("DEPRECATION")
|
||||||
|
requireArguments().getParcelable(FIELD_SE_ID)!!
|
||||||
|
}
|
||||||
val <T> T.isUsb: Boolean
|
val <T> T.isUsb: Boolean
|
||||||
where T : Fragment, T : EuiccChannelFragmentMarker
|
where T : Fragment, T : EuiccChannelFragmentMarker
|
||||||
get() = slotId == EuiccChannelManager.USB_CHANNEL_ID
|
get() = slotId == EuiccChannelManager.USB_CHANNEL_ID
|
||||||
|
@ -54,7 +75,12 @@ val <T> T.euiccChannelManagerService: EuiccChannelManagerService
|
||||||
suspend fun <T, R> T.withEuiccChannel(fn: suspend (EuiccChannel) -> R): R
|
suspend fun <T, R> T.withEuiccChannel(fn: suspend (EuiccChannel) -> R): R
|
||||||
where T : Fragment, T : EuiccChannelFragmentMarker {
|
where T : Fragment, T : EuiccChannelFragmentMarker {
|
||||||
ensureEuiccChannelManager()
|
ensureEuiccChannelManager()
|
||||||
return euiccChannelManager.withEuiccChannel(slotId, portId, fn)
|
return euiccChannelManager.withEuiccChannel(
|
||||||
|
slotId,
|
||||||
|
portId,
|
||||||
|
seId,
|
||||||
|
fn
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun <T> T.ensureEuiccChannelManager() where T : Fragment, T : OpenEuiccContextMarker =
|
suspend fun <T> T.ensureEuiccChannelManager() where T : Fragment, T : OpenEuiccContextMarker =
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package im.angry.openeuicc.di
|
package im.angry.openeuicc.di
|
||||||
|
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
|
import im.angry.openeuicc.core.EuiccChannel
|
||||||
import im.angry.openeuicc.ui.EuiccManagementFragment
|
import im.angry.openeuicc.ui.EuiccManagementFragment
|
||||||
import im.angry.openeuicc.ui.SettingsFragment
|
import im.angry.openeuicc.ui.SettingsFragment
|
||||||
import im.angry.openeuicc.ui.UnprivilegedEuiccManagementFragment
|
import im.angry.openeuicc.ui.UnprivilegedEuiccManagementFragment
|
||||||
|
@ -8,8 +9,12 @@ import im.angry.openeuicc.ui.UnprivilegedNoEuiccPlaceholderFragment
|
||||||
import im.angry.openeuicc.ui.UnprivilegedSettingsFragment
|
import im.angry.openeuicc.ui.UnprivilegedSettingsFragment
|
||||||
|
|
||||||
class UnprivilegedUiComponentFactory : DefaultUiComponentFactory() {
|
class UnprivilegedUiComponentFactory : DefaultUiComponentFactory() {
|
||||||
override fun createEuiccManagementFragment(slotId: Int, portId: Int): EuiccManagementFragment =
|
override fun createEuiccManagementFragment(
|
||||||
UnprivilegedEuiccManagementFragment.newInstance(slotId, portId)
|
slotId: Int,
|
||||||
|
portId: Int,
|
||||||
|
seId: EuiccChannel.SecureElementId
|
||||||
|
): EuiccManagementFragment =
|
||||||
|
UnprivilegedEuiccManagementFragment.newInstance(slotId, portId, seId)
|
||||||
|
|
||||||
override fun createNoEuiccPlaceholderFragment(): Fragment =
|
override fun createNoEuiccPlaceholderFragment(): Fragment =
|
||||||
UnprivilegedNoEuiccPlaceholderFragment()
|
UnprivilegedNoEuiccPlaceholderFragment()
|
||||||
|
|
|
@ -7,6 +7,7 @@ import android.view.MenuInflater
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import im.angry.easyeuicc.R
|
import im.angry.easyeuicc.R
|
||||||
|
import im.angry.openeuicc.core.EuiccChannel
|
||||||
import im.angry.openeuicc.util.SIMToolkit
|
import im.angry.openeuicc.util.SIMToolkit
|
||||||
import im.angry.openeuicc.util.newInstanceEuicc
|
import im.angry.openeuicc.util.newInstanceEuicc
|
||||||
import im.angry.openeuicc.util.slotId
|
import im.angry.openeuicc.util.slotId
|
||||||
|
@ -16,8 +17,12 @@ class UnprivilegedEuiccManagementFragment : EuiccManagementFragment() {
|
||||||
companion object {
|
companion object {
|
||||||
const val TAG = "UnprivilegedEuiccManagementFragment"
|
const val TAG = "UnprivilegedEuiccManagementFragment"
|
||||||
|
|
||||||
fun newInstance(slotId: Int, portId: Int): EuiccManagementFragment =
|
fun newInstance(
|
||||||
newInstanceEuicc(UnprivilegedEuiccManagementFragment::class.java, slotId, portId)
|
slotId: Int,
|
||||||
|
portId: Int,
|
||||||
|
seId: EuiccChannel.SecureElementId
|
||||||
|
): EuiccManagementFragment =
|
||||||
|
newInstanceEuicc(UnprivilegedEuiccManagementFragment::class.java, slotId, portId, seId)
|
||||||
}
|
}
|
||||||
|
|
||||||
private val stk by lazy {
|
private val stk by lazy {
|
||||||
|
|
|
@ -16,13 +16,14 @@ class PrivilegedEuiccChannelFactory(context: Context) : DefaultEuiccChannelFacto
|
||||||
@Suppress("NAME_SHADOWING")
|
@Suppress("NAME_SHADOWING")
|
||||||
override suspend fun tryOpenEuiccChannel(
|
override suspend fun tryOpenEuiccChannel(
|
||||||
port: UiccPortInfoCompat,
|
port: UiccPortInfoCompat,
|
||||||
isdrAid: ByteArray
|
isdrAid: ByteArray,
|
||||||
|
seId: EuiccChannel.SecureElementId,
|
||||||
): EuiccChannel? {
|
): EuiccChannel? {
|
||||||
val port = port as RealUiccPortInfoCompat
|
val port = port as RealUiccPortInfoCompat
|
||||||
if (port.card.isRemovable) {
|
if (port.card.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
|
||||||
super.tryOpenEuiccChannel(port, isdrAid)?.let { return it }
|
super.tryOpenEuiccChannel(port, isdrAid, seId)?.let { return it }
|
||||||
}
|
}
|
||||||
|
|
||||||
if (port.card.isEuicc || preferenceRepository.removableTelephonyManagerFlow.first()) {
|
if (port.card.isEuicc || preferenceRepository.removableTelephonyManagerFlow.first()) {
|
||||||
|
@ -41,6 +42,7 @@ class PrivilegedEuiccChannelFactory(context: Context) : DefaultEuiccChannelFacto
|
||||||
context.preferenceRepository.verboseLoggingFlow
|
context.preferenceRepository.verboseLoggingFlow
|
||||||
),
|
),
|
||||||
isdrAid,
|
isdrAid,
|
||||||
|
seId,
|
||||||
context.preferenceRepository.verboseLoggingFlow,
|
context.preferenceRepository.verboseLoggingFlow,
|
||||||
context.preferenceRepository.ignoreTLSCertificateFlow,
|
context.preferenceRepository.ignoreTLSCertificateFlow,
|
||||||
)
|
)
|
||||||
|
@ -53,6 +55,6 @@ class PrivilegedEuiccChannelFactory(context: Context) : DefaultEuiccChannelFacto
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return super.tryOpenEuiccChannel(port, isdrAid)
|
return super.tryOpenEuiccChannel(port, isdrAid, seId)
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,13 +1,18 @@
|
||||||
package im.angry.openeuicc.di
|
package im.angry.openeuicc.di
|
||||||
|
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
|
import im.angry.openeuicc.core.EuiccChannel
|
||||||
import im.angry.openeuicc.ui.EuiccManagementFragment
|
import im.angry.openeuicc.ui.EuiccManagementFragment
|
||||||
import im.angry.openeuicc.ui.PrivilegedEuiccManagementFragment
|
import im.angry.openeuicc.ui.PrivilegedEuiccManagementFragment
|
||||||
import im.angry.openeuicc.ui.PrivilegedSettingsFragment
|
import im.angry.openeuicc.ui.PrivilegedSettingsFragment
|
||||||
|
|
||||||
class PrivilegedUiComponentFactory : DefaultUiComponentFactory() {
|
class PrivilegedUiComponentFactory : DefaultUiComponentFactory() {
|
||||||
override fun createEuiccManagementFragment(slotId: Int, portId: Int): EuiccManagementFragment =
|
override fun createEuiccManagementFragment(
|
||||||
PrivilegedEuiccManagementFragment.newInstance(slotId, portId)
|
slotId: Int,
|
||||||
|
portId: Int,
|
||||||
|
seId: EuiccChannel.SecureElementId
|
||||||
|
): EuiccManagementFragment =
|
||||||
|
PrivilegedEuiccManagementFragment.newInstance(slotId, portId, seId)
|
||||||
|
|
||||||
override fun createSettingsFragment(): Fragment =
|
override fun createSettingsFragment(): Fragment =
|
||||||
PrivilegedSettingsFragment()
|
PrivilegedSettingsFragment()
|
||||||
|
|
|
@ -5,13 +5,18 @@ import android.view.ViewGroup
|
||||||
import android.widget.Button
|
import android.widget.Button
|
||||||
import android.widget.PopupMenu
|
import android.widget.PopupMenu
|
||||||
import im.angry.openeuicc.R
|
import im.angry.openeuicc.R
|
||||||
|
import im.angry.openeuicc.core.EuiccChannel
|
||||||
import im.angry.openeuicc.util.*
|
import im.angry.openeuicc.util.*
|
||||||
import net.typeblog.lpac_jni.LocalProfileInfo
|
import net.typeblog.lpac_jni.LocalProfileInfo
|
||||||
|
|
||||||
class PrivilegedEuiccManagementFragment: EuiccManagementFragment() {
|
class PrivilegedEuiccManagementFragment : EuiccManagementFragment() {
|
||||||
companion object {
|
companion object {
|
||||||
fun newInstance(slotId: Int, portId: Int): EuiccManagementFragment =
|
fun newInstance(
|
||||||
newInstanceEuicc(PrivilegedEuiccManagementFragment::class.java, slotId, portId)
|
slotId: Int,
|
||||||
|
portId: Int,
|
||||||
|
seId: EuiccChannel.SecureElementId
|
||||||
|
): EuiccManagementFragment =
|
||||||
|
newInstanceEuicc(PrivilegedEuiccManagementFragment::class.java, slotId, portId, seId)
|
||||||
}
|
}
|
||||||
|
|
||||||
private var isMEP = false
|
private var isMEP = false
|
||||||
|
|
Loading…
Add table
Reference in a new issue