refactor: Wrap EuiccChannelManager in an Android Service instance
All checks were successful
/ build-debug (push) Successful in 4m54s

This allows MUCH better lifecycle control over EuiccChannelManager. We
no longer have to keep all opened APDU channels open until the
application is destroyed. Instead, they can be closed as long as no
component is bound to this Service instance.

A catch is that other long-running services must bind to this service
as-needed, otherwise a binding is going to keep the service always
alive. This only affects the EuiccService implementation, and a
suspending/blocking helper function is added to deal with this case.
This commit is contained in:
Peter Cai 2024-05-04 17:29:10 -04:00
parent 0f655f1f1f
commit 59f3597874
19 changed files with 256 additions and 49 deletions

View file

@ -28,5 +28,9 @@
android:name="com.journeyapps.barcodescanner.CaptureActivity" android:name="com.journeyapps.barcodescanner.CaptureActivity"
android:screenOrientation="fullSensor" android:screenOrientation="fullSensor"
tools:replace="screenOrientation" /> tools:replace="screenOrientation" />
<service
android:name="im.angry.openeuicc.service.EuiccChannelManagerService"
android:exported="false" />
</application> </application>
</manifest> </manifest>

View file

@ -0,0 +1,10 @@
package im.angry.openeuicc.core
import android.app.Service
import im.angry.openeuicc.di.AppContainer
class DefaultEuiccChannelManagerFactory(private val appContainer: AppContainer) :
EuiccChannelManagerFactory {
override fun createEuiccChannelManager(serviceContext: Service) =
DefaultEuiccChannelManager(appContainer, serviceContext)
}

View file

@ -0,0 +1,7 @@
package im.angry.openeuicc.core
import android.app.Service
interface EuiccChannelManagerFactory {
fun createEuiccChannelManager(serviceContext: Service): EuiccChannelManager
}

View file

@ -4,11 +4,13 @@ import android.telephony.SubscriptionManager
import android.telephony.TelephonyManager import android.telephony.TelephonyManager
import im.angry.openeuicc.core.EuiccChannelFactory import im.angry.openeuicc.core.EuiccChannelFactory
import im.angry.openeuicc.core.EuiccChannelManager import im.angry.openeuicc.core.EuiccChannelManager
import im.angry.openeuicc.core.EuiccChannelManagerFactory
import im.angry.openeuicc.util.* import im.angry.openeuicc.util.*
interface AppContainer { interface AppContainer {
val telephonyManager: TelephonyManager val telephonyManager: TelephonyManager
val euiccChannelManager: EuiccChannelManager val euiccChannelManager: EuiccChannelManager
val euiccChannelManagerFactory: EuiccChannelManagerFactory
val subscriptionManager: SubscriptionManager val subscriptionManager: SubscriptionManager
val preferenceRepository: PreferenceRepository val preferenceRepository: PreferenceRepository
val uiComponentFactory: UiComponentFactory val uiComponentFactory: UiComponentFactory

View file

@ -5,7 +5,9 @@ import android.telephony.SubscriptionManager
import android.telephony.TelephonyManager import android.telephony.TelephonyManager
import im.angry.openeuicc.core.DefaultEuiccChannelFactory import im.angry.openeuicc.core.DefaultEuiccChannelFactory
import im.angry.openeuicc.core.DefaultEuiccChannelManager import im.angry.openeuicc.core.DefaultEuiccChannelManager
import im.angry.openeuicc.core.DefaultEuiccChannelManagerFactory
import im.angry.openeuicc.core.EuiccChannelManager import im.angry.openeuicc.core.EuiccChannelManager
import im.angry.openeuicc.core.EuiccChannelManagerFactory
import im.angry.openeuicc.util.* import im.angry.openeuicc.util.*
open class DefaultAppContainer(context: Context) : AppContainer { open class DefaultAppContainer(context: Context) : AppContainer {
@ -17,6 +19,10 @@ open class DefaultAppContainer(context: Context) : AppContainer {
DefaultEuiccChannelManager(this, context) DefaultEuiccChannelManager(this, context)
} }
override val euiccChannelManagerFactory: EuiccChannelManagerFactory by lazy {
DefaultEuiccChannelManagerFactory(this)
}
override val subscriptionManager by lazy { override val subscriptionManager by lazy {
context.getSystemService(SubscriptionManager::class.java)!! context.getSystemService(SubscriptionManager::class.java)!!
} }

View file

@ -0,0 +1,41 @@
package im.angry.openeuicc.service
import android.app.Service
import android.content.Intent
import android.os.Binder
import android.os.IBinder
import im.angry.openeuicc.core.EuiccChannelManager
import im.angry.openeuicc.util.*
/**
* An Android Service wrapper for EuiccChannelManager.
* The purpose of this wrapper is mainly lifecycle-wise: having a Service allows the manager
* instance to have its own independent lifecycle. This way it can be created as requested and
* destroyed when no other components are bound to this service anymore.
* This behavior allows us to avoid keeping the APDU channels open at all times. For example,
* the EuiccService implementation should *only* bind to this service when it requires an
* instance of EuiccChannelManager. UI components can keep being bound to this service for
* their entire lifecycles, since the whole purpose of them is to expose the current state
* to the user.
*/
class EuiccChannelManagerService : Service(), OpenEuiccContextMarker {
inner class LocalBinder : Binder() {
val service = this@EuiccChannelManagerService
}
private val euiccChannelManagerDelegate = lazy {
appContainer.euiccChannelManagerFactory.createEuiccChannelManager(this)
}
val euiccChannelManager: EuiccChannelManager by euiccChannelManagerDelegate
override fun onBind(intent: Intent?): IBinder = LocalBinder()
override fun onDestroy() {
super.onDestroy()
// This is the whole reason of the existence of this service:
// we can clean up opened channels when no one is using them
if (euiccChannelManagerDelegate.isInitialized()) {
euiccChannelManager.invalidate()
}
}
}

View file

@ -0,0 +1,48 @@
package im.angry.openeuicc.ui
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.ServiceConnection
import android.os.Bundle
import android.os.IBinder
import androidx.appcompat.app.AppCompatActivity
import im.angry.openeuicc.core.EuiccChannelManager
import im.angry.openeuicc.service.EuiccChannelManagerService
abstract class BaseEuiccAccessActivity : AppCompatActivity() {
lateinit var euiccChannelManager: EuiccChannelManager
private val euiccChannelManagerServiceConnection = object : ServiceConnection {
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
euiccChannelManager =
(service!! as EuiccChannelManagerService.LocalBinder).service.euiccChannelManager
onInit()
}
override fun onServiceDisconnected(name: ComponentName?) {
// These activities should never lose the EuiccChannelManagerService connection
finish()
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
bindService(
Intent(this, EuiccChannelManagerService::class.java),
euiccChannelManagerServiceConnection,
Context.BIND_AUTO_CREATE
)
}
override fun onDestroy() {
super.onDestroy()
unbindService(euiccChannelManagerServiceConnection)
}
/**
* When called, euiccChannelManager is guaranteed to have been initialized
*/
abstract fun onInit()
}

View file

@ -1,16 +1,13 @@
package im.angry.openeuicc.ui package im.angry.openeuicc.ui
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import im.angry.openeuicc.util.* import im.angry.openeuicc.util.*
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
class DirectProfileDownloadActivity : AppCompatActivity(), SlotSelectFragment.SlotSelectedListener, OpenEuiccContextMarker { class DirectProfileDownloadActivity : BaseEuiccAccessActivity(), SlotSelectFragment.SlotSelectedListener, OpenEuiccContextMarker {
override fun onCreate(savedInstanceState: Bundle?) { override fun onInit() {
super.onCreate(savedInstanceState)
lifecycleScope.launch { lifecycleScope.launch {
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
euiccChannelManager.enumerateEuiccChannels() euiccChannelManager.enumerateEuiccChannels()

View file

@ -10,16 +10,14 @@ import android.view.View
import android.widget.AdapterView import android.widget.AdapterView
import android.widget.ArrayAdapter import android.widget.ArrayAdapter
import android.widget.Spinner import android.widget.Spinner
import androidx.appcompat.app.AppCompatActivity
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.util.* import im.angry.openeuicc.util.*
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
open class MainActivity : AppCompatActivity(), OpenEuiccContextMarker { open class MainActivity : BaseEuiccAccessActivity(), OpenEuiccContextMarker {
companion object { companion object {
const val TAG = "MainActivity" const val TAG = "MainActivity"
} }
@ -45,10 +43,6 @@ open class MainActivity : AppCompatActivity(), OpenEuiccContextMarker {
tm = telephonyManager tm = telephonyManager
spinnerAdapter = ArrayAdapter<String>(this, R.layout.spinner_item) spinnerAdapter = ArrayAdapter<String>(this, R.layout.spinner_item)
lifecycleScope.launch {
init()
}
} }
override fun onCreateOptionsMenu(menu: Menu): Boolean { override fun onCreateOptionsMenu(menu: Menu): Boolean {
@ -94,6 +88,12 @@ open class MainActivity : AppCompatActivity(), OpenEuiccContextMarker {
else -> super.onOptionsItemSelected(item) else -> super.onOptionsItemSelected(item)
} }
override fun onInit() {
lifecycleScope.launch {
init()
}
}
private suspend fun init() { private suspend fun init() {
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
euiccChannelManager.enumerateEuiccChannels() euiccChannelManager.enumerateEuiccChannels()

View file

@ -12,7 +12,6 @@ import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.TextView import android.widget.TextView
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.forEach import androidx.core.view.forEach
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.DividerItemDecoration import androidx.recyclerview.widget.DividerItemDecoration
@ -27,7 +26,7 @@ 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: AppCompatActivity(), 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()
@ -39,7 +38,9 @@ class NotificationsActivity: AppCompatActivity(), OpenEuiccContextMarker {
setContentView(R.layout.activity_notifications) setContentView(R.layout.activity_notifications)
setSupportActionBar(requireViewById(R.id.toolbar)) setSupportActionBar(requireViewById(R.id.toolbar))
supportActionBar!!.setDisplayHomeAsUpEnabled(true) supportActionBar!!.setDisplayHomeAsUpEnabled(true)
}
override fun onInit() {
euiccChannel = euiccChannelManager euiccChannel = euiccChannelManager
.findEuiccChannelBySlotBlocking(intent.getIntExtra("logicalSlotId", 0))!! .findEuiccChannelBySlotBlocking(intent.getIntExtra("logicalSlotId", 0))!!

View file

@ -29,7 +29,8 @@ class SlotSelectFragment : BaseMaterialDialogFragment(), OpenEuiccContextMarker
private lateinit var toolbar: Toolbar private lateinit var toolbar: Toolbar
private lateinit var spinner: Spinner private lateinit var spinner: Spinner
private val channels: List<EuiccChannel> by lazy { private val channels: List<EuiccChannel> by lazy {
euiccChannelManager.knownChannels.sortedBy { it.logicalSlotId } (requireActivity() as BaseEuiccAccessActivity).euiccChannelManager
.knownChannels.sortedBy { it.logicalSlotId }
} }
override fun onCreateView( override fun onCreateView(

View file

@ -4,9 +4,10 @@ import android.os.Bundle
import android.util.Log 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 im.angry.openeuicc.core.EuiccChannelManager
import im.angry.openeuicc.ui.BaseEuiccAccessActivity
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import net.typeblog.lpac_jni.LocalProfileAssistant
private const val TAG = "EuiccChannelFragmentUtils" private const val TAG = "EuiccChannelFragmentUtils"
@ -30,6 +31,8 @@ val <T> T.slotId: Int where T: Fragment, T: EuiccChannelFragmentMarker
val <T> T.portId: Int where T: Fragment, T: EuiccChannelFragmentMarker val <T> T.portId: Int where T: Fragment, T: EuiccChannelFragmentMarker
get() = requireArguments().getInt("portId") get() = requireArguments().getInt("portId")
val <T> T.euiccChannelManager: EuiccChannelManager where T: Fragment, T: EuiccChannelFragmentMarker
get() = (requireActivity() as BaseEuiccAccessActivity).euiccChannelManager
val <T> T.channel: EuiccChannel where T: Fragment, T: EuiccChannelFragmentMarker val <T> T.channel: EuiccChannel where T: Fragment, T: EuiccChannelFragmentMarker
get() = get() =
euiccChannelManager.findEuiccChannelByPortBlocking(slotId, portId)!! euiccChannelManager.findEuiccChannelByPortBlocking(slotId, portId)!!

View file

@ -7,7 +7,6 @@ import android.telephony.TelephonyManager
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import im.angry.openeuicc.OpenEuiccApplication import im.angry.openeuicc.OpenEuiccApplication
import im.angry.openeuicc.core.EuiccChannel import im.angry.openeuicc.core.EuiccChannel
import im.angry.openeuicc.core.EuiccChannelManager
import im.angry.openeuicc.di.AppContainer import im.angry.openeuicc.di.AppContainer
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
@ -15,7 +14,7 @@ import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.sync.withLock
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import net.typeblog.lpac_jni.LocalProfileInfo import net.typeblog.lpac_jni.LocalProfileInfo
import java.lang.RuntimeException import kotlin.RuntimeException
import kotlin.coroutines.resume import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException import kotlin.coroutines.resumeWithException
import kotlin.coroutines.suspendCoroutine import kotlin.coroutines.suspendCoroutine
@ -52,9 +51,6 @@ interface OpenEuiccContextMarker {
val appContainer: AppContainer val appContainer: AppContainer
get() = openEuiccApplication.appContainer get() = openEuiccApplication.appContainer
val euiccChannelManager: EuiccChannelManager
get() = appContainer.euiccChannelManager
val telephonyManager: TelephonyManager val telephonyManager: TelephonyManager
get() = appContainer.telephonyManager get() = appContainer.telephonyManager
} }

View file

@ -5,7 +5,10 @@ import im.angry.openeuicc.di.AppContainer
import im.angry.openeuicc.util.* import im.angry.openeuicc.util.*
import java.lang.Exception import java.lang.Exception
class PrivilegedEuiccChannelManager(appContainer: AppContainer, context: Context) : class PrivilegedEuiccChannelManager(
appContainer: AppContainer,
context: Context
) :
DefaultEuiccChannelManager(appContainer, context) { DefaultEuiccChannelManager(appContainer, context) {
override val uiccCards: Collection<UiccCardInfoCompat> override val uiccCards: Collection<UiccCardInfoCompat>
get() = tm.uiccCardsInfoCompat get() = tm.uiccCardsInfoCompat

View file

@ -0,0 +1,10 @@
package im.angry.openeuicc.core
import android.app.Service
import im.angry.openeuicc.di.AppContainer
class PrivilegedEuiccChannelManagerFactory(private val appContainer: AppContainer) :
EuiccChannelManagerFactory {
override fun createEuiccChannelManager(serviceContext: Service): EuiccChannelManager =
PrivilegedEuiccChannelManager(appContainer, serviceContext)
}

View file

@ -2,14 +2,20 @@ package im.angry.openeuicc.di
import android.content.Context import android.content.Context
import im.angry.openeuicc.core.EuiccChannelManager import im.angry.openeuicc.core.EuiccChannelManager
import im.angry.openeuicc.core.EuiccChannelManagerFactory
import im.angry.openeuicc.core.PrivilegedEuiccChannelFactory import im.angry.openeuicc.core.PrivilegedEuiccChannelFactory
import im.angry.openeuicc.core.PrivilegedEuiccChannelManager import im.angry.openeuicc.core.PrivilegedEuiccChannelManager
import im.angry.openeuicc.core.PrivilegedEuiccChannelManagerFactory
class PrivilegedAppContainer(context: Context) : DefaultAppContainer(context) { class PrivilegedAppContainer(context: Context) : DefaultAppContainer(context) {
override val euiccChannelManager: EuiccChannelManager by lazy { override val euiccChannelManager: EuiccChannelManager by lazy {
PrivilegedEuiccChannelManager(this, context) PrivilegedEuiccChannelManager(this, context)
} }
override val euiccChannelManagerFactory: EuiccChannelManagerFactory by lazy {
PrivilegedEuiccChannelManagerFactory(this)
}
override val uiComponentFactory by lazy { override val uiComponentFactory by lazy {
PrivilegedUiComponentFactory() PrivilegedUiComponentFactory()
} }

View file

@ -1,5 +1,7 @@
package im.angry.openeuicc.service package im.angry.openeuicc.service
import android.content.Context
import android.content.Intent
import android.os.Build import android.os.Build
import android.service.euicc.* import android.service.euicc.*
import android.telephony.UiccSlotMapping import android.telephony.UiccSlotMapping
@ -8,7 +10,9 @@ import android.telephony.euicc.EuiccInfo
import android.util.Log import android.util.Log
import net.typeblog.lpac_jni.LocalProfileInfo import net.typeblog.lpac_jni.LocalProfileInfo
import im.angry.openeuicc.core.EuiccChannel import im.angry.openeuicc.core.EuiccChannel
import im.angry.openeuicc.core.EuiccChannelManager
import im.angry.openeuicc.util.* import im.angry.openeuicc.util.*
import kotlinx.coroutines.runBlocking
import java.lang.IllegalStateException import java.lang.IllegalStateException
class OpenEuiccService : EuiccService(), OpenEuiccContextMarker { class OpenEuiccService : EuiccService(), OpenEuiccContextMarker {
@ -31,17 +35,51 @@ class OpenEuiccService : EuiccService(), OpenEuiccContextMarker {
telephonyManager.uiccCardsInfoCompat.firstOrNull { it.isEuicc }?.physicalSlotIndex == physicalSlotId telephonyManager.uiccCardsInfoCompat.firstOrNull { it.isEuicc }?.physicalSlotIndex == physicalSlotId
} }
private fun findChannel(physicalSlotId: Int): EuiccChannel? = private data class EuiccChannelManagerContext(
euiccChannelManager.findEuiccChannelByPhysicalSlotBlocking(physicalSlotId) val euiccChannelManager: EuiccChannelManager
) {
fun findChannel(physicalSlotId: Int): EuiccChannel? =
euiccChannelManager.findEuiccChannelByPhysicalSlotBlocking(physicalSlotId)
private fun findChannel(slotId: Int, portId: Int): EuiccChannel? = fun findChannel(slotId: Int, portId: Int): EuiccChannel? =
euiccChannelManager.findEuiccChannelByPortBlocking(slotId, portId) euiccChannelManager.findEuiccChannelByPortBlocking(slotId, portId)
private fun findAllChannels(physicalSlotId: Int): List<EuiccChannel>? = fun findAllChannels(physicalSlotId: Int): List<EuiccChannel>? =
euiccChannelManager.findAllEuiccChannelsByPhysicalSlotBlocking(physicalSlotId) euiccChannelManager.findAllEuiccChannelsByPhysicalSlotBlocking(physicalSlotId)
}
override fun onGetEid(slotId: Int): String? = /**
* Bind to EuiccChannelManagerService, run the callback with a EuiccChannelManager instance,
* and then unbind after the callback is finished. All methods in this class that require access
* to a EuiccChannelManager should be wrapped inside this call.
*
* This ensures that we only spawn and connect to APDU channels when we absolutely need to,
* instead of keeping them open unnecessarily in the background at all times.
*/
private inline fun <T> withEuiccChannelManager(fn: EuiccChannelManagerContext.() -> T): T {
val (binder, unbind) = runBlocking {
bindServiceSuspended(
Intent(
this@OpenEuiccService,
EuiccChannelManagerService::class.java
), Context.BIND_AUTO_CREATE
)
}
if (binder == null) {
throw RuntimeException("Unable to bind to EuiccChannelManagerService; aborting")
}
val ret =
EuiccChannelManagerContext((binder as EuiccChannelManagerService.LocalBinder).service.euiccChannelManager).fn()
unbind()
return ret
}
override fun onGetEid(slotId: Int): String? = withEuiccChannelManager {
findChannel(slotId)?.lpa?.eID findChannel(slotId)?.lpa?.eID
}
// When two eSIM cards are present on one device, the Android settings UI // When two eSIM cards are present on one device, the Android settings UI
// gets confused and sets the incorrect slotId for profiles from one of // gets confused and sets the incorrect slotId for profiles from one of
@ -124,7 +162,7 @@ class OpenEuiccService : EuiccService(), OpenEuiccContextMarker {
return GetDefaultDownloadableSubscriptionListResult(RESULT_OK, arrayOf()) return GetDefaultDownloadableSubscriptionListResult(RESULT_OK, arrayOf())
} }
override fun onGetEuiccProfileInfoList(slotId: Int): GetEuiccProfileInfoListResult { override fun onGetEuiccProfileInfoList(slotId: Int): GetEuiccProfileInfoListResult = withEuiccChannelManager {
Log.i(TAG, "onGetEuiccProfileInfoList slotId=$slotId") Log.i(TAG, "onGetEuiccProfileInfoList slotId=$slotId")
if (shouldIgnoreSlot(slotId)) { if (shouldIgnoreSlot(slotId)) {
Log.i(TAG, "ignoring slot $slotId") Log.i(TAG, "ignoring slot $slotId")
@ -165,7 +203,7 @@ class OpenEuiccService : EuiccService(), OpenEuiccContextMarker {
return EuiccInfo("Unknown") // TODO: Can we actually implement this? return EuiccInfo("Unknown") // TODO: Can we actually implement this?
} }
override fun onDeleteSubscription(slotId: Int, iccid: String): Int { override fun onDeleteSubscription(slotId: Int, iccid: String): Int = withEuiccChannelManager {
Log.i(TAG, "onDeleteSubscription slotId=$slotId iccid=$iccid") Log.i(TAG, "onDeleteSubscription slotId=$slotId iccid=$iccid")
if (shouldIgnoreSlot(slotId)) return RESULT_FIRST_USER if (shouldIgnoreSlot(slotId)) return RESULT_FIRST_USER
@ -212,7 +250,7 @@ class OpenEuiccService : EuiccService(), OpenEuiccContextMarker {
portIndex: Int, portIndex: Int,
iccid: String?, iccid: String?,
forceDeactivateSim: Boolean forceDeactivateSim: Boolean
): Int { ): Int = withEuiccChannelManager {
Log.i(TAG,"onSwitchToSubscriptionWithPort slotId=$slotId portIndex=$portIndex iccid=$iccid forceDeactivateSim=$forceDeactivateSim") Log.i(TAG,"onSwitchToSubscriptionWithPort slotId=$slotId portIndex=$portIndex iccid=$iccid forceDeactivateSim=$forceDeactivateSim")
if (shouldIgnoreSlot(slotId)) return RESULT_FIRST_USER if (shouldIgnoreSlot(slotId)) return RESULT_FIRST_USER
@ -264,22 +302,26 @@ class OpenEuiccService : EuiccService(), OpenEuiccContextMarker {
} }
} }
override fun onUpdateSubscriptionNickname(slotId: Int, iccid: String, nickname: String?): Int { override fun onUpdateSubscriptionNickname(slotId: Int, iccid: String, nickname: String?): Int =
Log.i(TAG, "onUpdateSubscriptionNickname slotId=$slotId iccid=$iccid nickname=$nickname") withEuiccChannelManager {
if (shouldIgnoreSlot(slotId)) return RESULT_FIRST_USER Log.i(
val channel = findChannel(slotId) ?: return RESULT_FIRST_USER TAG,
if (!channel.profileExists(iccid)) { "onUpdateSubscriptionNickname slotId=$slotId iccid=$iccid nickname=$nickname"
return RESULT_FIRST_USER )
if (shouldIgnoreSlot(slotId)) return RESULT_FIRST_USER
val channel = findChannel(slotId) ?: return RESULT_FIRST_USER
if (!channel.profileExists(iccid)) {
return RESULT_FIRST_USER
}
val success = channel.lpa
.setNickname(iccid, nickname!!)
appContainer.subscriptionManager.tryRefreshCachedEuiccInfo(channel.cardId)
return if (success) {
RESULT_OK
} else {
RESULT_FIRST_USER
}
} }
val success = channel.lpa
.setNickname(iccid, nickname!!)
appContainer.subscriptionManager.tryRefreshCachedEuiccInfo(channel.cardId)
return if (success) {
RESULT_OK
} else {
RESULT_FIRST_USER
}
}
@Deprecated("Deprecated in Java") @Deprecated("Deprecated in Java")
override fun onEraseSubscriptions(slotId: Int): Int { override fun onEraseSubscriptions(slotId: Int): Int {

View file

@ -96,14 +96,17 @@ class SlotMappingFragment: BaseMaterialDialogFragment(),
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
// Use the utility method from PrivilegedTelephonyUtils to ensure // Use the utility method from PrivilegedTelephonyUtils to ensure
// unmapped ports have all profiles disabled // unmapped ports have all profiles disabled
telephonyManager.updateSimSlotMapping(euiccChannelManager, adapter.mappings) telephonyManager.updateSimSlotMapping(
(requireActivity() as BaseEuiccAccessActivity).euiccChannelManager,
adapter.mappings
)
} }
} catch (e: Exception) { } catch (e: Exception) {
Toast.makeText(requireContext(), R.string.slot_mapping_failure, Toast.LENGTH_LONG).show() Toast.makeText(requireContext(), R.string.slot_mapping_failure, Toast.LENGTH_LONG).show()
return@launch return@launch
} }
Toast.makeText(requireContext(), R.string.slot_mapping_completed, Toast.LENGTH_LONG).show() Toast.makeText(requireContext(), R.string.slot_mapping_completed, Toast.LENGTH_LONG).show()
euiccChannelManager.invalidate() (requireActivity() as BaseEuiccAccessActivity).euiccChannelManager.invalidate()
requireActivity().finish() requireActivity().finish()
} }
} }

View file

@ -0,0 +1,27 @@
package im.angry.openeuicc.util
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.ServiceConnection
import android.os.IBinder
import java.util.concurrent.Executors
import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine
suspend fun Context.bindServiceSuspended(intent: Intent, flags: Int): Pair<IBinder?, () -> Unit> =
suspendCoroutine { cont ->
var binder: IBinder?
val conn = object : ServiceConnection {
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
binder = service
cont.resume(Pair(binder) { unbindService(this) })
}
override fun onServiceDisconnected(name: ComponentName?) {
}
}
bindService(intent, flags, Executors.newSingleThreadExecutor(), conn)
}