OpenEuiccService.kt 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182
  1. package im.angry.openeuicc.service
  2. import android.service.euicc.*
  3. import android.telephony.euicc.DownloadableSubscription
  4. import android.telephony.euicc.EuiccInfo
  5. import com.truphone.lpa.LocalProfileInfo
  6. import com.truphone.lpad.progress.Progress
  7. import im.angry.openeuicc.OpenEuiccApplication
  8. import im.angry.openeuicc.core.EuiccChannel
  9. import im.angry.openeuicc.util.*
  10. class OpenEuiccService : EuiccService() {
  11. private val openEuiccApplication
  12. get() = application as OpenEuiccApplication
  13. private fun findChannel(slotId: Int): EuiccChannel? =
  14. openEuiccApplication.euiccChannelManager
  15. .findEuiccChannelBySlotBlocking(slotId)
  16. override fun onGetEid(slotId: Int): String? =
  17. findChannel(slotId)?.lpa?.eid
  18. // When two eSIM cards are present on one device, the Android settings UI
  19. // gets confused and sets the incorrect slotId for profiles from one of
  20. // the cards. This function helps Detect this case and abort early.
  21. private fun EuiccChannel.profileExists(iccid: String?) =
  22. lpa.profiles.any { it.iccid == iccid }
  23. override fun onGetOtaStatus(slotId: Int): Int {
  24. // Not implemented
  25. return 5 // EUICC_OTA_STATUS_UNAVAILABLE
  26. }
  27. override fun onStartOtaIfNecessary(
  28. slotId: Int,
  29. statusChangedCallback: OtaStatusChangedCallback?
  30. ) {
  31. // Not implemented
  32. }
  33. override fun onGetDownloadableSubscriptionMetadata(
  34. slotId: Int,
  35. subscription: DownloadableSubscription?,
  36. forceDeactivateSim: Boolean
  37. ): GetDownloadableSubscriptionMetadataResult {
  38. // Stub: return as-is and do not fetch anything
  39. // This is incompatible with carrier eSIM apps; should we make it compatible?
  40. return GetDownloadableSubscriptionMetadataResult(RESULT_OK, subscription)
  41. }
  42. override fun onGetDefaultDownloadableSubscriptionList(
  43. slotId: Int,
  44. forceDeactivateSim: Boolean
  45. ): GetDefaultDownloadableSubscriptionListResult {
  46. // Stub: we do not implement this (as this would require phoning in a central GSMA server)
  47. return GetDefaultDownloadableSubscriptionListResult(RESULT_OK, arrayOf())
  48. }
  49. override fun onGetEuiccProfileInfoList(slotId: Int): GetEuiccProfileInfoListResult? {
  50. val channel = findChannel(slotId) ?: return null
  51. val profiles = channel.lpa.profiles.operational.map {
  52. EuiccProfileInfo.Builder(it.iccid).apply {
  53. setProfileName(it.name)
  54. setNickname(it.displayName)
  55. setServiceProviderName(it.providerName)
  56. setState(
  57. when (it.state) {
  58. LocalProfileInfo.State.Enabled -> EuiccProfileInfo.PROFILE_STATE_ENABLED
  59. LocalProfileInfo.State.Disabled -> EuiccProfileInfo.PROFILE_STATE_DISABLED
  60. }
  61. )
  62. setProfileClass(
  63. when (it.profileClass) {
  64. LocalProfileInfo.Clazz.Testing -> EuiccProfileInfo.PROFILE_CLASS_TESTING
  65. LocalProfileInfo.Clazz.Provisioning -> EuiccProfileInfo.PROFILE_CLASS_PROVISIONING
  66. LocalProfileInfo.Clazz.Operational -> EuiccProfileInfo.PROFILE_CLASS_OPERATIONAL
  67. }
  68. )
  69. }.build()
  70. }
  71. return GetEuiccProfileInfoListResult(RESULT_OK, profiles.toTypedArray(), channel.removable)
  72. }
  73. override fun onGetEuiccInfo(slotId: Int): EuiccInfo {
  74. return EuiccInfo("Unknown") // TODO: Can we actually implement this?
  75. }
  76. override fun onDeleteSubscription(slotId: Int, iccid: String): Int {
  77. try {
  78. val channel = findChannel(slotId) ?: return RESULT_FIRST_USER
  79. if (!channel.profileExists(iccid)) {
  80. return RESULT_FIRST_USER
  81. }
  82. val profile = channel.lpa.profiles.find {
  83. it.iccid == iccid
  84. } ?: return RESULT_FIRST_USER
  85. if (profile.state == LocalProfileInfo.State.Enabled) {
  86. // Must disable the profile first
  87. return RESULT_FIRST_USER
  88. }
  89. return if (channel.lpa.deleteProfile(iccid, Progress()) == "0") {
  90. RESULT_OK
  91. } else {
  92. RESULT_FIRST_USER
  93. }
  94. } catch (e: Exception) {
  95. return RESULT_FIRST_USER
  96. }
  97. }
  98. // TODO: on some devices we need to update the mapping (and potentially disable a pSIM)
  99. // for eSIM to be usable, in which case we will have to respect forceDeactivateSim.
  100. // This is the same for our custom LUI. Both have to take this into consideration.
  101. @Deprecated("Deprecated in Java")
  102. override fun onSwitchToSubscription(
  103. slotId: Int,
  104. iccid: String?,
  105. forceDeactivateSim: Boolean
  106. ): Int {
  107. try {
  108. val channel = findChannel(slotId) ?: return RESULT_FIRST_USER
  109. if (!channel.profileExists(iccid)) {
  110. return RESULT_FIRST_USER
  111. }
  112. if (iccid == null) {
  113. // Disable active profile
  114. val activeProfile = channel.lpa.profiles.find {
  115. it.state == LocalProfileInfo.State.Enabled
  116. } ?: return RESULT_OK
  117. return if (channel.lpa.disableProfile(activeProfile.iccid, Progress()) == "0") {
  118. RESULT_OK
  119. } else {
  120. RESULT_FIRST_USER
  121. }
  122. } else {
  123. return if (channel.lpa.enableProfile(iccid, Progress()) == "0") {
  124. RESULT_OK
  125. } else {
  126. RESULT_FIRST_USER
  127. }
  128. }
  129. } catch (e: Exception) {
  130. return RESULT_FIRST_USER
  131. } finally {
  132. openEuiccApplication.euiccChannelManager.invalidate()
  133. }
  134. }
  135. override fun onUpdateSubscriptionNickname(slotId: Int, iccid: String, nickname: String?): Int {
  136. val channel = findChannel(slotId) ?: return RESULT_FIRST_USER
  137. if (!channel.profileExists(iccid)) {
  138. return RESULT_FIRST_USER
  139. }
  140. val success = channel.lpa
  141. .setNickname(iccid, nickname)
  142. openEuiccApplication.subscriptionManager.tryRefreshCachedEuiccInfo(channel.cardId)
  143. return if (success) {
  144. RESULT_OK
  145. } else {
  146. RESULT_FIRST_USER
  147. }
  148. }
  149. @Deprecated("Deprecated in Java")
  150. override fun onEraseSubscriptions(slotId: Int): Int {
  151. // No-op
  152. return RESULT_FIRST_USER
  153. }
  154. override fun onRetainSubscriptionsForFactoryReset(slotId: Int): Int {
  155. // No-op -- we do not care
  156. return RESULT_FIRST_USER
  157. }
  158. }