PrivilegedTelephonyUtils.kt 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293
  1. package im.angry.openeuicc.util
  2. import android.telephony.SubscriptionManager
  3. import android.telephony.TelephonyManager
  4. import android.telephony.UiccSlotMapping
  5. import im.angry.openeuicc.core.EuiccChannel
  6. import im.angry.openeuicc.core.EuiccChannelManager
  7. import kotlinx.coroutines.flow.onEach
  8. import kotlinx.coroutines.runBlocking
  9. import java.lang.Exception
  10. val TelephonyManager.supportsDSDS: Boolean
  11. get() = supportedModemCount == 2
  12. val TelephonyManager.dsdsEnabled: Boolean
  13. get() = activeModemCount >= 2
  14. fun TelephonyManager.setDsdsEnabled(euiccManager: EuiccChannelManager, enabled: Boolean) {
  15. // Disable all eSIM profiles before performing a DSDS switch (only for internal eSIMs)
  16. runBlocking {
  17. euiccManager.flowInternalEuiccPorts().onEach { (slotId, portId) ->
  18. euiccManager.withEuiccChannel(slotId, portId) {
  19. if (!it.port.card.isRemovable) {
  20. it.lpa.disableActiveProfile(false)
  21. }
  22. }
  23. }
  24. }
  25. switchMultiSimConfig(if (enabled) { 2 } else { 1 })
  26. }
  27. // Disable eSIM profiles before switching the slot mapping
  28. // This ensures that unmapped eSIM ports never have "ghost" profiles enabled
  29. suspend fun TelephonyManager.updateSimSlotMapping(
  30. euiccManager: EuiccChannelManager, newMapping: Collection<UiccSlotMapping>,
  31. currentMapping: Collection<UiccSlotMapping> = simSlotMapping
  32. ) {
  33. val unmapped = currentMapping.filterNot { mapping ->
  34. // If the same physical slot + port pair is not found in the new mapping, it is unmapped
  35. newMapping.any {
  36. it.physicalSlotIndex == mapping.physicalSlotIndex && it.portIndex == mapping.portIndex
  37. }
  38. }
  39. val undo: List<suspend () -> Unit> = unmapped.mapNotNull { mapping ->
  40. euiccManager.withEuiccChannel(mapping.physicalSlotIndex, mapping.portIndex) { channel ->
  41. if (!channel.port.card.isRemovable) {
  42. channel.lpa.disableActiveProfileKeepIccId(false)
  43. } else {
  44. // Do not do anything for external eUICCs -- we can't really trust them to work properly
  45. // with no profile enabled.
  46. null
  47. }
  48. }?.let { iccid ->
  49. // Generate undo closure because we can't keep reference to `channel` in the closure above
  50. {
  51. euiccManager.withEuiccChannel(
  52. mapping.physicalSlotIndex,
  53. mapping.portIndex
  54. ) { channel ->
  55. channel.lpa.enableProfile(iccid)
  56. }
  57. }
  58. }
  59. }
  60. try {
  61. simSlotMapping = newMapping
  62. } catch (e: Exception) {
  63. e.printStackTrace()
  64. undo.forEach { it() } // Undo what we just did
  65. throw e // Rethrow for caller to handle
  66. }
  67. }
  68. fun SubscriptionManager.tryRefreshCachedEuiccInfo(cardId: Int) {
  69. if (cardId != 0) {
  70. try {
  71. requestEmbeddedSubscriptionInfoListRefresh(cardId)
  72. } catch (e: Exception) {
  73. // Ignore
  74. }
  75. }
  76. }
  77. // Every EuiccChannel we use here should be backed by a RealUiccPortInfoCompat
  78. // except when it is from a USB card reader
  79. val EuiccChannel.cardId
  80. get() = (port as? RealUiccPortInfoCompat)?.card?.cardId ?: -1
  81. val EuiccChannel.isMEP
  82. get() = (port as? RealUiccPortInfoCompat)?.card?.isMultipleEnabledProfilesSupported ?: false