Compare commits
2 commits
92d8f9079f
...
1ac683f9ab
Author | SHA1 | Date | |
---|---|---|---|
|
1ac683f9ab | ||
|
7834e0348a |
|
@ -10,7 +10,7 @@ open class DefaultEuiccChannelFactory(protected val context: Context) : EuiccCha
|
||||||
private var seService: SEService? = null
|
private var seService: SEService? = null
|
||||||
|
|
||||||
private suspend fun ensureSEService() {
|
private suspend fun ensureSEService() {
|
||||||
if (seService == null) {
|
if (seService == null || !seService!!.isConnected) {
|
||||||
seService = connectSEService(context)
|
seService = connectSEService(context)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,10 +6,12 @@ import android.util.Log
|
||||||
import im.angry.openeuicc.di.AppContainer
|
import im.angry.openeuicc.di.AppContainer
|
||||||
import im.angry.openeuicc.util.*
|
import im.angry.openeuicc.util.*
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import kotlinx.coroutines.sync.Mutex
|
import kotlinx.coroutines.sync.Mutex
|
||||||
import kotlinx.coroutines.sync.withLock
|
import kotlinx.coroutines.sync.withLock
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
|
import kotlinx.coroutines.withTimeout
|
||||||
|
|
||||||
open class DefaultEuiccChannelManager(
|
open class DefaultEuiccChannelManager(
|
||||||
protected val appContainer: AppContainer,
|
protected val appContainer: AppContainer,
|
||||||
|
@ -87,14 +89,18 @@ open class DefaultEuiccChannelManager(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override suspend fun findAllEuiccChannelsByPhysicalSlot(physicalSlotId: Int): List<EuiccChannel>? {
|
||||||
|
for (card in uiccCards) {
|
||||||
|
if (card.physicalSlotIndex != physicalSlotId) continue
|
||||||
|
return card.ports.mapNotNull { tryOpenEuiccChannel(it) }
|
||||||
|
.ifEmpty { null }
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
override fun findAllEuiccChannelsByPhysicalSlotBlocking(physicalSlotId: Int): List<EuiccChannel>? =
|
override fun findAllEuiccChannelsByPhysicalSlotBlocking(physicalSlotId: Int): List<EuiccChannel>? =
|
||||||
runBlocking {
|
runBlocking {
|
||||||
for (card in uiccCards) {
|
findAllEuiccChannelsByPhysicalSlot(physicalSlotId)
|
||||||
if (card.physicalSlotIndex != physicalSlotId) continue
|
|
||||||
return@runBlocking card.ports.mapNotNull { tryOpenEuiccChannel(it) }
|
|
||||||
.ifEmpty { null }
|
|
||||||
}
|
|
||||||
return@runBlocking null
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun findEuiccChannelByPortBlocking(physicalSlotId: Int, portId: Int): EuiccChannel? =
|
override fun findEuiccChannelByPortBlocking(physicalSlotId: Int, portId: Int): EuiccChannel? =
|
||||||
|
@ -106,6 +112,25 @@ open class DefaultEuiccChannelManager(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override suspend fun tryReconnectSlot(physicalSlotId: Int, timeoutMillis: Long) {
|
||||||
|
invalidateByPhysicalSlot(physicalSlotId)
|
||||||
|
|
||||||
|
withTimeout(timeoutMillis) {
|
||||||
|
while (true) {
|
||||||
|
try {
|
||||||
|
findAllEuiccChannelsByPhysicalSlot(physicalSlotId)!!.forEach {
|
||||||
|
check(it.valid) { "Invalid channel" }
|
||||||
|
}
|
||||||
|
break
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.d(TAG, "Slot reconnect failure, retrying in 1000 ms")
|
||||||
|
invalidateByPhysicalSlot(physicalSlotId)
|
||||||
|
}
|
||||||
|
delay(1000)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override suspend fun enumerateEuiccChannels() {
|
override suspend fun enumerateEuiccChannels() {
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
for (uiccInfo in uiccCards) {
|
for (uiccInfo in uiccCards) {
|
||||||
|
@ -132,4 +157,12 @@ open class DefaultEuiccChannelManager(
|
||||||
channels.clear()
|
channels.clear()
|
||||||
euiccChannelFactory.cleanup()
|
euiccChannelFactory.cleanup()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun invalidateByPhysicalSlot(physicalSlotId: Int) {
|
||||||
|
val toRemove = channels.filter { it.valid && it.slotId == physicalSlotId }
|
||||||
|
for (channel in toRemove) {
|
||||||
|
channel.close()
|
||||||
|
channels.remove(channel)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -8,6 +8,15 @@ interface EuiccChannelManager {
|
||||||
*/
|
*/
|
||||||
suspend fun enumerateEuiccChannels()
|
suspend fun enumerateEuiccChannels()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reconnect ALL EuiccChannels belonging to a physical slot
|
||||||
|
* Throws TimeoutCancellationException when timed out
|
||||||
|
* If this operation times out, none of the channels belonging to the slot will be
|
||||||
|
* guaranteed to be consistent. The caller should either call invalidate()
|
||||||
|
* and try again later, or the application should simply exit entirely.
|
||||||
|
*/
|
||||||
|
suspend fun tryReconnectSlot(physicalSlotId: Int, timeoutMillis: Long = 1000)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the EuiccChannel corresponding to a **logical** slot
|
* Returns the EuiccChannel corresponding to a **logical** slot
|
||||||
*/
|
*/
|
||||||
|
@ -24,6 +33,7 @@ interface EuiccChannelManager {
|
||||||
* Returns all EuiccChannels corresponding to a **physical** slot
|
* Returns all EuiccChannels corresponding to a **physical** slot
|
||||||
* Multiple channels are possible in the case of MEP
|
* Multiple channels are possible in the case of MEP
|
||||||
*/
|
*/
|
||||||
|
suspend fun findAllEuiccChannelsByPhysicalSlot(physicalSlotId: Int): List<EuiccChannel>?
|
||||||
fun findAllEuiccChannelsByPhysicalSlotBlocking(physicalSlotId: Int): List<EuiccChannel>?
|
fun findAllEuiccChannelsByPhysicalSlotBlocking(physicalSlotId: Int): List<EuiccChannel>?
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -31,7 +31,6 @@ import kotlinx.coroutines.TimeoutCancellationException
|
||||||
import kotlinx.coroutines.flow.first
|
import kotlinx.coroutines.flow.first
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import java.lang.Exception
|
|
||||||
|
|
||||||
open class EuiccManagementFragment : Fragment(), EuiccProfilesChangedListener,
|
open class EuiccManagementFragment : Fragment(), EuiccProfilesChangedListener,
|
||||||
EuiccChannelFragmentMarker {
|
EuiccChannelFragmentMarker {
|
||||||
|
@ -132,48 +131,51 @@ open class EuiccManagementFragment : Fragment(), EuiccProfilesChangedListener,
|
||||||
fab.isEnabled = false
|
fab.isEnabled = false
|
||||||
|
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
try {
|
beginTrackedOperation {
|
||||||
if (enable) {
|
val res = if (enable) {
|
||||||
doEnableProfile(iccid)
|
channel.lpa.enableProfile(iccid)
|
||||||
} else {
|
} else {
|
||||||
doDisableProfile(iccid)
|
channel.lpa.disableProfile(iccid)
|
||||||
}
|
}
|
||||||
refresh()
|
|
||||||
fab.isEnabled = true
|
if (!res) {
|
||||||
} catch (e: TimeoutCancellationException) {
|
Log.d(TAG, "Failed to enable / disable profile $iccid")
|
||||||
// Timed out waiting for SIM to come back online, we can no longer assume that the LPA is still valid
|
Toast.makeText(context, R.string.toast_profile_enable_failed, Toast.LENGTH_LONG)
|
||||||
AlertDialog.Builder(requireContext()).apply {
|
.show()
|
||||||
setMessage(R.string.enable_disable_timeout)
|
return@beginTrackedOperation false
|
||||||
setPositiveButton(android.R.string.ok) { dialog, _ ->
|
}
|
||||||
dialog.dismiss()
|
|
||||||
requireActivity().finish()
|
try {
|
||||||
}
|
euiccChannelManager.tryReconnectSlot(slotId, timeoutMillis = 30 * 1000)
|
||||||
setOnDismissListener { _ ->
|
} catch (e: TimeoutCancellationException) {
|
||||||
requireActivity().finish()
|
withContext(Dispatchers.Main) {
|
||||||
}
|
// Timed out waiting for SIM to come back online, we can no longer assume that the LPA is still valid
|
||||||
show()
|
AlertDialog.Builder(requireContext()).apply {
|
||||||
|
setMessage(R.string.enable_disable_timeout)
|
||||||
|
setPositiveButton(android.R.string.ok) { dialog, _ ->
|
||||||
|
dialog.dismiss()
|
||||||
|
requireActivity().finish()
|
||||||
|
}
|
||||||
|
setOnDismissListener { _ ->
|
||||||
|
requireActivity().finish()
|
||||||
|
}
|
||||||
|
show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return@beginTrackedOperation false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (enable) {
|
||||||
|
preferenceRepository.notificationEnableFlow.first()
|
||||||
|
} else {
|
||||||
|
preferenceRepository.notificationDisableFlow.first()
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
|
||||||
Log.d(TAG, "Failed to enable / disable profile $iccid")
|
|
||||||
Log.d(TAG, Log.getStackTraceString(e))
|
|
||||||
fab.isEnabled = true
|
|
||||||
Toast.makeText(context, R.string.toast_profile_enable_failed, Toast.LENGTH_LONG).show()
|
|
||||||
}
|
}
|
||||||
|
refresh()
|
||||||
|
fab.isEnabled = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun doEnableProfile(iccid: String) =
|
|
||||||
channel.lpa.beginOperation {
|
|
||||||
channel.lpa.enableProfile(iccid, reconnectTimeout = 15 * 1000) &&
|
|
||||||
preferenceRepository.notificationEnableFlow.first()
|
|
||||||
}
|
|
||||||
|
|
||||||
private suspend fun doDisableProfile(iccid: String) =
|
|
||||||
channel.lpa.beginOperation {
|
|
||||||
channel.lpa.disableProfile(iccid, reconnectTimeout = 15 * 1000) &&
|
|
||||||
preferenceRepository.notificationDisableFlow.first()
|
|
||||||
}
|
|
||||||
|
|
||||||
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) {
|
||||||
|
|
|
@ -82,7 +82,7 @@ class ProfileDeleteFragment : DialogFragment(), EuiccChannelFragmentMarker {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun doDelete() = channel.lpa.beginOperation {
|
private suspend fun doDelete() = beginTrackedOperation {
|
||||||
channel.lpa.deleteProfile(requireArguments().getString("iccid")!!)
|
channel.lpa.deleteProfile(requireArguments().getString("iccid")!!)
|
||||||
preferenceRepository.notificationDeleteFlow.first()
|
preferenceRepository.notificationDeleteFlow.first()
|
||||||
}
|
}
|
||||||
|
|
|
@ -189,15 +189,25 @@ class ProfileDownloadFragment : BaseMaterialDialogFragment(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun doDownloadProfile(server: String, code: String?, confirmationCode: String?, imei: String?) = channel.lpa.beginOperation {
|
private suspend fun doDownloadProfile(
|
||||||
downloadProfile(server, code, imei, confirmationCode, object : ProfileDownloadCallback {
|
server: String,
|
||||||
override fun onStateUpdate(state: ProfileDownloadCallback.DownloadState) {
|
code: String?,
|
||||||
lifecycleScope.launch(Dispatchers.Main) {
|
confirmationCode: String?,
|
||||||
progress.isIndeterminate = false
|
imei: String?
|
||||||
progress.progress = state.progress
|
) = beginTrackedOperation {
|
||||||
|
channel.lpa.downloadProfile(
|
||||||
|
server,
|
||||||
|
code,
|
||||||
|
imei,
|
||||||
|
confirmationCode,
|
||||||
|
object : ProfileDownloadCallback {
|
||||||
|
override fun onStateUpdate(state: ProfileDownloadCallback.DownloadState) {
|
||||||
|
lifecycleScope.launch(Dispatchers.Main) {
|
||||||
|
progress.isIndeterminate = false
|
||||||
|
progress.progress = state.progress
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
})
|
|
||||||
|
|
||||||
// If we get here, we are successful
|
// If we get here, we are successful
|
||||||
// Only send notifications if the user allowed us to
|
// Only send notifications if the user allowed us to
|
||||||
|
|
|
@ -1,8 +1,14 @@
|
||||||
package im.angry.openeuicc.util
|
package im.angry.openeuicc.util
|
||||||
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import android.util.Log
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import im.angry.openeuicc.core.EuiccChannel
|
import im.angry.openeuicc.core.EuiccChannel
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
import net.typeblog.lpac_jni.LocalProfileAssistant
|
||||||
|
|
||||||
|
private const val TAG = "EuiccChannelFragmentUtils"
|
||||||
|
|
||||||
interface EuiccChannelFragmentMarker: OpenEuiccContextMarker
|
interface EuiccChannelFragmentMarker: OpenEuiccContextMarker
|
||||||
|
|
||||||
|
@ -28,6 +34,27 @@ val <T> T.channel: EuiccChannel where T: Fragment, T: EuiccChannelFragmentMarker
|
||||||
get() =
|
get() =
|
||||||
euiccChannelManager.findEuiccChannelByPortBlocking(slotId, portId)!!
|
euiccChannelManager.findEuiccChannelByPortBlocking(slotId, portId)!!
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Begin a "tracked" operation where notifications may be generated by the eSIM
|
||||||
|
* Automatically handle any newly generated notification during the operation
|
||||||
|
* if the function "op" returns true.
|
||||||
|
*/
|
||||||
|
suspend fun <T> T.beginTrackedOperation(op: suspend () -> Boolean) where T : Fragment, T : EuiccChannelFragmentMarker =
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
|
val latestSeq = channel.lpa.notifications.firstOrNull()?.seqNumber ?: 0
|
||||||
|
Log.d(TAG, "Latest notification is $latestSeq before operation")
|
||||||
|
if (op()) {
|
||||||
|
Log.d(TAG, "Operation has requested notification handling")
|
||||||
|
// Note that the exact instance of "channel" might have changed here if reconnected;
|
||||||
|
// so we MUST use the automatic getter for "channel"
|
||||||
|
channel.lpa.notifications.filter { it.seqNumber > latestSeq }.forEach {
|
||||||
|
Log.d(TAG, "Handling notification $it")
|
||||||
|
channel.lpa.handleNotification(it.seqNumber)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Log.d(TAG, "Operation complete")
|
||||||
|
}
|
||||||
|
|
||||||
interface EuiccProfilesChangedListener {
|
interface EuiccProfilesChangedListener {
|
||||||
fun onEuiccProfilesChanged()
|
fun onEuiccProfilesChanged()
|
||||||
}
|
}
|
|
@ -15,7 +15,8 @@ class UnprivilegedOpenEuiccApplication : OpenEuiccApplication() {
|
||||||
override fun onCreate() {
|
override fun onCreate() {
|
||||||
super.onCreate()
|
super.onCreate()
|
||||||
|
|
||||||
Thread.setDefaultUncaughtExceptionHandler { _, _ ->
|
Thread.setDefaultUncaughtExceptionHandler { _, e ->
|
||||||
|
e.printStackTrace()
|
||||||
Intent(this, LogsActivity::class.java).apply {
|
Intent(this, LogsActivity::class.java).apply {
|
||||||
addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK)
|
addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK)
|
||||||
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||||
|
|
|
@ -1,14 +1,6 @@
|
||||||
package net.typeblog.lpac_jni
|
package net.typeblog.lpac_jni
|
||||||
|
|
||||||
import android.util.Log
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.withContext
|
|
||||||
|
|
||||||
interface LocalProfileAssistant {
|
interface LocalProfileAssistant {
|
||||||
companion object {
|
|
||||||
private const val TAG = "LocalProfileAssistant"
|
|
||||||
}
|
|
||||||
|
|
||||||
val valid: Boolean
|
val valid: Boolean
|
||||||
get() = try {
|
get() = try {
|
||||||
// If we can read both eID and profiles properly, we are likely looking at
|
// If we can read both eID and profiles properly, we are likely looking at
|
||||||
|
@ -27,8 +19,8 @@ interface LocalProfileAssistant {
|
||||||
|
|
||||||
// All blocking functions in this class assume that they are executed on non-Main threads
|
// All blocking functions in this class assume that they are executed on non-Main threads
|
||||||
// The IO context in Kotlin's coroutine library is recommended.
|
// The IO context in Kotlin's coroutine library is recommended.
|
||||||
fun enableProfile(iccid: String, reconnectTimeout: Long = 0): Boolean
|
fun enableProfile(iccid: String): Boolean
|
||||||
fun disableProfile(iccid: String, reconnectTimeout: Long = 0): Boolean
|
fun disableProfile(iccid: String): Boolean
|
||||||
fun deleteProfile(iccid: String): Boolean
|
fun deleteProfile(iccid: String): Boolean
|
||||||
|
|
||||||
fun downloadProfile(smdp: String, matchingId: String?, imei: String?,
|
fun downloadProfile(smdp: String, matchingId: String?, imei: String?,
|
||||||
|
@ -37,24 +29,6 @@ interface LocalProfileAssistant {
|
||||||
fun deleteNotification(seqNumber: Long): Boolean
|
fun deleteNotification(seqNumber: Long): Boolean
|
||||||
fun handleNotification(seqNumber: Long): Boolean
|
fun handleNotification(seqNumber: Long): Boolean
|
||||||
|
|
||||||
// Wraps an operation on the eSIM chip (any of the other blocking functions)
|
|
||||||
// Handles notifications automatically after the operation, unless the lambda executing
|
|
||||||
// the operation returns false, which inhibits automatic notification processing.
|
|
||||||
// All code executed within are also wrapped automatically in the IO context.
|
|
||||||
suspend fun beginOperation(op: suspend LocalProfileAssistant.() -> Boolean) =
|
|
||||||
withContext(Dispatchers.IO) {
|
|
||||||
val latestSeq = notifications.firstOrNull()?.seqNumber ?: 0
|
|
||||||
Log.d(TAG, "Latest notification is $latestSeq before operation")
|
|
||||||
if (op(this@LocalProfileAssistant)) {
|
|
||||||
Log.d(TAG, "Operation has requested notification handling")
|
|
||||||
notifications.filter { it.seqNumber > latestSeq }.forEach {
|
|
||||||
Log.d(TAG, "Handling notification $it")
|
|
||||||
handleNotification(it.seqNumber)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Log.d(TAG, "Operation complete")
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setNickname(
|
fun setNickname(
|
||||||
iccid: String, nickname: String
|
iccid: String, nickname: String
|
||||||
): Boolean
|
): Boolean
|
||||||
|
|
|
@ -1,9 +1,6 @@
|
||||||
package net.typeblog.lpac_jni.impl
|
package net.typeblog.lpac_jni.impl
|
||||||
|
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import kotlinx.coroutines.delay
|
|
||||||
import kotlinx.coroutines.runBlocking
|
|
||||||
import kotlinx.coroutines.withTimeout
|
|
||||||
import net.typeblog.lpac_jni.LpacJni
|
import net.typeblog.lpac_jni.LpacJni
|
||||||
import net.typeblog.lpac_jni.ApduInterface
|
import net.typeblog.lpac_jni.ApduInterface
|
||||||
import net.typeblog.lpac_jni.EuiccInfo2
|
import net.typeblog.lpac_jni.EuiccInfo2
|
||||||
|
@ -14,8 +11,8 @@ import net.typeblog.lpac_jni.LocalProfileNotification
|
||||||
import net.typeblog.lpac_jni.ProfileDownloadCallback
|
import net.typeblog.lpac_jni.ProfileDownloadCallback
|
||||||
|
|
||||||
class LocalProfileAssistantImpl(
|
class LocalProfileAssistantImpl(
|
||||||
private val apduInterface: ApduInterface,
|
apduInterface: ApduInterface,
|
||||||
private val httpInterface: HttpInterface
|
httpInterface: HttpInterface
|
||||||
): LocalProfileAssistant {
|
): LocalProfileAssistant {
|
||||||
companion object {
|
companion object {
|
||||||
private const val TAG = "LocalProfileAssistantImpl"
|
private const val TAG = "LocalProfileAssistantImpl"
|
||||||
|
@ -31,48 +28,6 @@ class LocalProfileAssistantImpl(
|
||||||
httpInterface.usePublicKeyIds(pkids)
|
httpInterface.usePublicKeyIds(pkids)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun tryReconnect(timeoutMillis: Long) = runBlocking {
|
|
||||||
withTimeout(timeoutMillis) {
|
|
||||||
try {
|
|
||||||
LpacJni.euiccFini(contextHandle)
|
|
||||||
LpacJni.destroyContext(contextHandle)
|
|
||||||
contextHandle = -1
|
|
||||||
} catch (e: Exception) {
|
|
||||||
// Ignored
|
|
||||||
}
|
|
||||||
|
|
||||||
while (true) {
|
|
||||||
try {
|
|
||||||
apduInterface.disconnect()
|
|
||||||
} catch (e: Exception) {
|
|
||||||
// Ignored
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
apduInterface.connect()
|
|
||||||
contextHandle = LpacJni.createContext(apduInterface, httpInterface)
|
|
||||||
check(LpacJni.euiccInit(contextHandle) >= 0) { "Reconnect attempt failed" }
|
|
||||||
// Validate that we can actually use the APDU channel by trying to read eID and profiles
|
|
||||||
check(valid) { "Reconnected channel is invalid" }
|
|
||||||
break
|
|
||||||
} catch (e: Exception) {
|
|
||||||
e.printStackTrace()
|
|
||||||
if (contextHandle != -1L) {
|
|
||||||
try {
|
|
||||||
LpacJni.euiccFini(contextHandle)
|
|
||||||
LpacJni.destroyContext(contextHandle)
|
|
||||||
contextHandle = -1
|
|
||||||
} catch (e: Exception) {
|
|
||||||
// Ignored
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// continue retrying
|
|
||||||
delay(1000)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override val profiles: List<LocalProfileInfo>
|
override val profiles: List<LocalProfileInfo>
|
||||||
get() = LpacJni.es10cGetProfilesInfo(contextHandle)!!.asList()
|
get() = LpacJni.es10cGetProfilesInfo(contextHandle)!!.asList()
|
||||||
|
|
||||||
|
@ -87,21 +42,11 @@ class LocalProfileAssistantImpl(
|
||||||
override val euiccInfo2: EuiccInfo2?
|
override val euiccInfo2: EuiccInfo2?
|
||||||
get() = LpacJni.es10cexGetEuiccInfo2(contextHandle)
|
get() = LpacJni.es10cexGetEuiccInfo2(contextHandle)
|
||||||
|
|
||||||
override fun enableProfile(iccid: String, reconnectTimeout: Long): Boolean {
|
override fun enableProfile(iccid: String): Boolean =
|
||||||
val res = LpacJni.es10cEnableProfile(contextHandle, iccid) == 0
|
LpacJni.es10cEnableProfile(contextHandle, iccid) == 0
|
||||||
if (reconnectTimeout > 0) {
|
|
||||||
tryReconnect(reconnectTimeout)
|
|
||||||
}
|
|
||||||
return res
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun disableProfile(iccid: String, reconnectTimeout: Long): Boolean {
|
override fun disableProfile(iccid: String): Boolean =
|
||||||
val res = LpacJni.es10cDisableProfile(contextHandle, iccid) == 0
|
LpacJni.es10cDisableProfile(contextHandle, iccid) == 0
|
||||||
if (reconnectTimeout > 0) {
|
|
||||||
tryReconnect(reconnectTimeout)
|
|
||||||
}
|
|
||||||
return res
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun deleteProfile(iccid: String): Boolean {
|
override fun deleteProfile(iccid: String): Boolean {
|
||||||
return LpacJni.es10cDeleteProfile(contextHandle, iccid) == 0
|
return LpacJni.es10cDeleteProfile(contextHandle, iccid) == 0
|
||||||
|
|
Loading…
Reference in a new issue