LPAUtils.kt 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142
  1. package im.angry.openeuicc.util
  2. import android.util.Log
  3. import im.angry.openeuicc.core.EuiccChannel
  4. import im.angry.openeuicc.core.EuiccChannelManager
  5. import kotlinx.coroutines.Dispatchers
  6. import kotlinx.coroutines.runBlocking
  7. import kotlinx.coroutines.withContext
  8. import net.typeblog.lpac_jni.LocalProfileAssistant
  9. import net.typeblog.lpac_jni.LocalProfileInfo
  10. const val TAG = "LPAUtils"
  11. val LocalProfileInfo.displayName: String
  12. get() = nickName.ifEmpty { name }
  13. val LocalProfileInfo.isEnabled: Boolean
  14. get() = state == LocalProfileInfo.State.Enabled
  15. val List<LocalProfileInfo>.operational: List<LocalProfileInfo>
  16. get() = filter {
  17. it.profileClass == LocalProfileInfo.Clazz.Operational
  18. }
  19. val List<EuiccChannel>.hasMultipleChips: Boolean
  20. get() = distinctBy { it.slotId }.size > 1
  21. fun LocalProfileAssistant.switchProfile(
  22. iccid: String,
  23. enable: Boolean = false,
  24. refresh: Boolean = false
  25. ): Boolean =
  26. if (enable) {
  27. enableProfile(iccid, refresh)
  28. } else {
  29. disableProfile(iccid, refresh)
  30. }
  31. /**
  32. * Disable the current active profile if any. If refresh is true, also cause a refresh command.
  33. * See EuiccManager.waitForReconnect()
  34. */
  35. fun LocalProfileAssistant.disableActiveProfile(refresh: Boolean): Boolean =
  36. profiles.find { it.isEnabled }?.let {
  37. Log.i(TAG, "Disabling active profile ${it.iccid}")
  38. disableProfile(it.iccid, refresh)
  39. } ?: true
  40. /**
  41. * Disable the active profile, return a lambda that reverts this action when called.
  42. * If refreshOnDisable is true, also cause a eUICC refresh command. Note that refreshing
  43. * will disconnect the eUICC and might need some time before being operational again.
  44. * See EuiccManager.waitForReconnect()
  45. */
  46. fun LocalProfileAssistant.disableActiveProfileWithUndo(refreshOnDisable: Boolean): () -> Unit =
  47. profiles.find { it.isEnabled }?.let {
  48. disableProfile(it.iccid, refreshOnDisable)
  49. return { enableProfile(it.iccid) }
  50. } ?: { }
  51. /**
  52. * Begin a "tracked" operation where notifications may be generated by the eSIM
  53. * Automatically handle any newly generated notification during the operation
  54. * if the function "op" returns true.
  55. *
  56. * This requires the EuiccChannelManager object and a slotId / portId instead of
  57. * just an LPA object, because a LPA might become invalid during an operation
  58. * that generates notifications. As such, we will end up having to reconnect
  59. * when this happens.
  60. *
  61. * Note that however, if reconnect is required and will not be instant, waiting
  62. * should be the concern of op() itself, and this function assumes that when
  63. * op() returns, the slotId and portId will correspond to a valid channel again.
  64. */
  65. suspend inline fun EuiccChannelManager.beginTrackedOperation(
  66. slotId: Int,
  67. portId: Int,
  68. op: () -> Boolean
  69. ) {
  70. val latestSeq =
  71. findEuiccChannelByPort(slotId, portId)!!.lpa.notifications.firstOrNull()?.seqNumber
  72. ?: 0
  73. Log.d(TAG, "Latest notification is $latestSeq before operation")
  74. if (op()) {
  75. Log.d(TAG, "Operation has requested notification handling")
  76. try {
  77. // Note that the exact instance of "channel" might have changed here if reconnected;
  78. // so we MUST use the automatic getter for "channel"
  79. findEuiccChannelByPort(
  80. slotId,
  81. portId
  82. )?.lpa?.notifications?.filter { it.seqNumber > latestSeq }?.forEach {
  83. Log.d(TAG, "Handling notification $it")
  84. findEuiccChannelByPort(
  85. slotId,
  86. portId
  87. )?.lpa?.handleNotification(it.seqNumber)
  88. }
  89. } catch (e: Exception) {
  90. // Ignore any error during notification handling
  91. e.printStackTrace()
  92. }
  93. }
  94. Log.d(TAG, "Operation complete")
  95. }
  96. /**
  97. * Same as beginTrackedOperation but uses blocking primitives.
  98. * TODO: This function needs to be phased out of use.
  99. */
  100. inline fun EuiccChannelManager.beginTrackedOperationBlocking(
  101. slotId: Int,
  102. portId: Int,
  103. op: () -> Boolean
  104. ) {
  105. val latestSeq =
  106. findEuiccChannelByPortBlocking(slotId, portId)!!.lpa.notifications.firstOrNull()?.seqNumber
  107. ?: 0
  108. Log.d(TAG, "Latest notification is $latestSeq before operation")
  109. if (op()) {
  110. Log.d(TAG, "Operation has requested notification handling")
  111. try {
  112. // Note that the exact instance of "channel" might have changed here if reconnected;
  113. // so we MUST use the automatic getter for "channel"
  114. findEuiccChannelByPortBlocking(
  115. slotId,
  116. portId
  117. )?.lpa?.notifications?.filter { it.seqNumber > latestSeq }?.forEach {
  118. Log.d(TAG, "Handling notification $it")
  119. findEuiccChannelByPortBlocking(
  120. slotId,
  121. portId
  122. )?.lpa?.handleNotification(it.seqNumber)
  123. }
  124. } catch (e: Exception) {
  125. // Ignore any error during notification handling
  126. e.printStackTrace()
  127. }
  128. }
  129. Log.d(TAG, "Operation complete")
  130. }