refactor: Wrap EuiccChannelManager in an Android Service instance
All checks were successful
/ build-debug (push) Successful in 4m54s
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:
parent
0f655f1f1f
commit
59f3597874
|
@ -28,5 +28,9 @@
|
|||
android:name="com.journeyapps.barcodescanner.CaptureActivity"
|
||||
android:screenOrientation="fullSensor"
|
||||
tools:replace="screenOrientation" />
|
||||
|
||||
<service
|
||||
android:name="im.angry.openeuicc.service.EuiccChannelManagerService"
|
||||
android:exported="false" />
|
||||
</application>
|
||||
</manifest>
|
||||
|
|
|
@ -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)
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
package im.angry.openeuicc.core
|
||||
|
||||
import android.app.Service
|
||||
|
||||
interface EuiccChannelManagerFactory {
|
||||
fun createEuiccChannelManager(serviceContext: Service): EuiccChannelManager
|
||||
}
|
|
@ -4,11 +4,13 @@ import android.telephony.SubscriptionManager
|
|||
import android.telephony.TelephonyManager
|
||||
import im.angry.openeuicc.core.EuiccChannelFactory
|
||||
import im.angry.openeuicc.core.EuiccChannelManager
|
||||
import im.angry.openeuicc.core.EuiccChannelManagerFactory
|
||||
import im.angry.openeuicc.util.*
|
||||
|
||||
interface AppContainer {
|
||||
val telephonyManager: TelephonyManager
|
||||
val euiccChannelManager: EuiccChannelManager
|
||||
val euiccChannelManagerFactory: EuiccChannelManagerFactory
|
||||
val subscriptionManager: SubscriptionManager
|
||||
val preferenceRepository: PreferenceRepository
|
||||
val uiComponentFactory: UiComponentFactory
|
||||
|
|
|
@ -5,7 +5,9 @@ import android.telephony.SubscriptionManager
|
|||
import android.telephony.TelephonyManager
|
||||
import im.angry.openeuicc.core.DefaultEuiccChannelFactory
|
||||
import im.angry.openeuicc.core.DefaultEuiccChannelManager
|
||||
import im.angry.openeuicc.core.DefaultEuiccChannelManagerFactory
|
||||
import im.angry.openeuicc.core.EuiccChannelManager
|
||||
import im.angry.openeuicc.core.EuiccChannelManagerFactory
|
||||
import im.angry.openeuicc.util.*
|
||||
|
||||
open class DefaultAppContainer(context: Context) : AppContainer {
|
||||
|
@ -17,6 +19,10 @@ open class DefaultAppContainer(context: Context) : AppContainer {
|
|||
DefaultEuiccChannelManager(this, context)
|
||||
}
|
||||
|
||||
override val euiccChannelManagerFactory: EuiccChannelManagerFactory by lazy {
|
||||
DefaultEuiccChannelManagerFactory(this)
|
||||
}
|
||||
|
||||
override val subscriptionManager by lazy {
|
||||
context.getSystemService(SubscriptionManager::class.java)!!
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
}
|
|
@ -1,16 +1,13 @@
|
|||
package im.angry.openeuicc.ui
|
||||
|
||||
import android.os.Bundle
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import im.angry.openeuicc.util.*
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
class DirectProfileDownloadActivity : AppCompatActivity(), SlotSelectFragment.SlotSelectedListener, OpenEuiccContextMarker {
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
class DirectProfileDownloadActivity : BaseEuiccAccessActivity(), SlotSelectFragment.SlotSelectedListener, OpenEuiccContextMarker {
|
||||
override fun onInit() {
|
||||
lifecycleScope.launch {
|
||||
withContext(Dispatchers.IO) {
|
||||
euiccChannelManager.enumerateEuiccChannels()
|
||||
|
|
|
@ -10,16 +10,14 @@ import android.view.View
|
|||
import android.widget.AdapterView
|
||||
import android.widget.ArrayAdapter
|
||||
import android.widget.Spinner
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import im.angry.openeuicc.common.R
|
||||
import im.angry.openeuicc.core.EuiccChannel
|
||||
import im.angry.openeuicc.util.*
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
open class MainActivity : AppCompatActivity(), OpenEuiccContextMarker {
|
||||
open class MainActivity : BaseEuiccAccessActivity(), OpenEuiccContextMarker {
|
||||
companion object {
|
||||
const val TAG = "MainActivity"
|
||||
}
|
||||
|
@ -45,10 +43,6 @@ open class MainActivity : AppCompatActivity(), OpenEuiccContextMarker {
|
|||
tm = telephonyManager
|
||||
|
||||
spinnerAdapter = ArrayAdapter<String>(this, R.layout.spinner_item)
|
||||
|
||||
lifecycleScope.launch {
|
||||
init()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
||||
|
@ -94,6 +88,12 @@ open class MainActivity : AppCompatActivity(), OpenEuiccContextMarker {
|
|||
else -> super.onOptionsItemSelected(item)
|
||||
}
|
||||
|
||||
override fun onInit() {
|
||||
lifecycleScope.launch {
|
||||
init()
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun init() {
|
||||
withContext(Dispatchers.IO) {
|
||||
euiccChannelManager.enumerateEuiccChannels()
|
||||
|
|
|
@ -12,7 +12,6 @@ import android.view.View
|
|||
import android.view.ViewGroup
|
||||
import android.widget.TextView
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.view.forEach
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.recyclerview.widget.DividerItemDecoration
|
||||
|
@ -27,7 +26,7 @@ import kotlinx.coroutines.launch
|
|||
import kotlinx.coroutines.withContext
|
||||
import net.typeblog.lpac_jni.LocalProfileNotification
|
||||
|
||||
class NotificationsActivity: AppCompatActivity(), OpenEuiccContextMarker {
|
||||
class NotificationsActivity: BaseEuiccAccessActivity(), OpenEuiccContextMarker {
|
||||
private lateinit var swipeRefresh: SwipeRefreshLayout
|
||||
private lateinit var notificationList: RecyclerView
|
||||
private val notificationAdapter = NotificationAdapter()
|
||||
|
@ -39,7 +38,9 @@ class NotificationsActivity: AppCompatActivity(), OpenEuiccContextMarker {
|
|||
setContentView(R.layout.activity_notifications)
|
||||
setSupportActionBar(requireViewById(R.id.toolbar))
|
||||
supportActionBar!!.setDisplayHomeAsUpEnabled(true)
|
||||
}
|
||||
|
||||
override fun onInit() {
|
||||
euiccChannel = euiccChannelManager
|
||||
.findEuiccChannelBySlotBlocking(intent.getIntExtra("logicalSlotId", 0))!!
|
||||
|
||||
|
|
|
@ -29,7 +29,8 @@ class SlotSelectFragment : BaseMaterialDialogFragment(), OpenEuiccContextMarker
|
|||
private lateinit var toolbar: Toolbar
|
||||
private lateinit var spinner: Spinner
|
||||
private val channels: List<EuiccChannel> by lazy {
|
||||
euiccChannelManager.knownChannels.sortedBy { it.logicalSlotId }
|
||||
(requireActivity() as BaseEuiccAccessActivity).euiccChannelManager
|
||||
.knownChannels.sortedBy { it.logicalSlotId }
|
||||
}
|
||||
|
||||
override fun onCreateView(
|
||||
|
|
|
@ -4,9 +4,10 @@ import android.os.Bundle
|
|||
import android.util.Log
|
||||
import androidx.fragment.app.Fragment
|
||||
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.withContext
|
||||
import net.typeblog.lpac_jni.LocalProfileAssistant
|
||||
|
||||
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
|
||||
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
|
||||
get() =
|
||||
euiccChannelManager.findEuiccChannelByPortBlocking(slotId, portId)!!
|
||||
|
|
|
@ -7,7 +7,6 @@ import android.telephony.TelephonyManager
|
|||
import androidx.fragment.app.Fragment
|
||||
import im.angry.openeuicc.OpenEuiccApplication
|
||||
import im.angry.openeuicc.core.EuiccChannel
|
||||
import im.angry.openeuicc.core.EuiccChannelManager
|
||||
import im.angry.openeuicc.di.AppContainer
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.runBlocking
|
||||
|
@ -15,7 +14,7 @@ import kotlinx.coroutines.sync.Mutex
|
|||
import kotlinx.coroutines.sync.withLock
|
||||
import kotlinx.coroutines.withContext
|
||||
import net.typeblog.lpac_jni.LocalProfileInfo
|
||||
import java.lang.RuntimeException
|
||||
import kotlin.RuntimeException
|
||||
import kotlin.coroutines.resume
|
||||
import kotlin.coroutines.resumeWithException
|
||||
import kotlin.coroutines.suspendCoroutine
|
||||
|
@ -52,9 +51,6 @@ interface OpenEuiccContextMarker {
|
|||
val appContainer: AppContainer
|
||||
get() = openEuiccApplication.appContainer
|
||||
|
||||
val euiccChannelManager: EuiccChannelManager
|
||||
get() = appContainer.euiccChannelManager
|
||||
|
||||
val telephonyManager: TelephonyManager
|
||||
get() = appContainer.telephonyManager
|
||||
}
|
||||
|
|
|
@ -5,7 +5,10 @@ import im.angry.openeuicc.di.AppContainer
|
|||
import im.angry.openeuicc.util.*
|
||||
import java.lang.Exception
|
||||
|
||||
class PrivilegedEuiccChannelManager(appContainer: AppContainer, context: Context) :
|
||||
class PrivilegedEuiccChannelManager(
|
||||
appContainer: AppContainer,
|
||||
context: Context
|
||||
) :
|
||||
DefaultEuiccChannelManager(appContainer, context) {
|
||||
override val uiccCards: Collection<UiccCardInfoCompat>
|
||||
get() = tm.uiccCardsInfoCompat
|
||||
|
|
|
@ -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)
|
||||
}
|
|
@ -2,14 +2,20 @@ package im.angry.openeuicc.di
|
|||
|
||||
import android.content.Context
|
||||
import im.angry.openeuicc.core.EuiccChannelManager
|
||||
import im.angry.openeuicc.core.EuiccChannelManagerFactory
|
||||
import im.angry.openeuicc.core.PrivilegedEuiccChannelFactory
|
||||
import im.angry.openeuicc.core.PrivilegedEuiccChannelManager
|
||||
import im.angry.openeuicc.core.PrivilegedEuiccChannelManagerFactory
|
||||
|
||||
class PrivilegedAppContainer(context: Context) : DefaultAppContainer(context) {
|
||||
override val euiccChannelManager: EuiccChannelManager by lazy {
|
||||
PrivilegedEuiccChannelManager(this, context)
|
||||
}
|
||||
|
||||
override val euiccChannelManagerFactory: EuiccChannelManagerFactory by lazy {
|
||||
PrivilegedEuiccChannelManagerFactory(this)
|
||||
}
|
||||
|
||||
override val uiComponentFactory by lazy {
|
||||
PrivilegedUiComponentFactory()
|
||||
}
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
package im.angry.openeuicc.service
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Build
|
||||
import android.service.euicc.*
|
||||
import android.telephony.UiccSlotMapping
|
||||
|
@ -8,7 +10,9 @@ import android.telephony.euicc.EuiccInfo
|
|||
import android.util.Log
|
||||
import net.typeblog.lpac_jni.LocalProfileInfo
|
||||
import im.angry.openeuicc.core.EuiccChannel
|
||||
import im.angry.openeuicc.core.EuiccChannelManager
|
||||
import im.angry.openeuicc.util.*
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import java.lang.IllegalStateException
|
||||
|
||||
class OpenEuiccService : EuiccService(), OpenEuiccContextMarker {
|
||||
|
@ -31,17 +35,51 @@ class OpenEuiccService : EuiccService(), OpenEuiccContextMarker {
|
|||
telephonyManager.uiccCardsInfoCompat.firstOrNull { it.isEuicc }?.physicalSlotIndex == physicalSlotId
|
||||
}
|
||||
|
||||
private fun findChannel(physicalSlotId: Int): EuiccChannel? =
|
||||
euiccChannelManager.findEuiccChannelByPhysicalSlotBlocking(physicalSlotId)
|
||||
private data class EuiccChannelManagerContext(
|
||||
val euiccChannelManager: EuiccChannelManager
|
||||
) {
|
||||
fun findChannel(physicalSlotId: Int): EuiccChannel? =
|
||||
euiccChannelManager.findEuiccChannelByPhysicalSlotBlocking(physicalSlotId)
|
||||
|
||||
private fun findChannel(slotId: Int, portId: Int): EuiccChannel? =
|
||||
euiccChannelManager.findEuiccChannelByPortBlocking(slotId, portId)
|
||||
fun findChannel(slotId: Int, portId: Int): EuiccChannel? =
|
||||
euiccChannelManager.findEuiccChannelByPortBlocking(slotId, portId)
|
||||
|
||||
private fun findAllChannels(physicalSlotId: Int): List<EuiccChannel>? =
|
||||
euiccChannelManager.findAllEuiccChannelsByPhysicalSlotBlocking(physicalSlotId)
|
||||
fun findAllChannels(physicalSlotId: Int): List<EuiccChannel>? =
|
||||
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
|
||||
}
|
||||
|
||||
// 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
|
||||
|
@ -124,7 +162,7 @@ class OpenEuiccService : EuiccService(), OpenEuiccContextMarker {
|
|||
return GetDefaultDownloadableSubscriptionListResult(RESULT_OK, arrayOf())
|
||||
}
|
||||
|
||||
override fun onGetEuiccProfileInfoList(slotId: Int): GetEuiccProfileInfoListResult {
|
||||
override fun onGetEuiccProfileInfoList(slotId: Int): GetEuiccProfileInfoListResult = withEuiccChannelManager {
|
||||
Log.i(TAG, "onGetEuiccProfileInfoList slotId=$slotId")
|
||||
if (shouldIgnoreSlot(slotId)) {
|
||||
Log.i(TAG, "ignoring slot $slotId")
|
||||
|
@ -165,7 +203,7 @@ class OpenEuiccService : EuiccService(), OpenEuiccContextMarker {
|
|||
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")
|
||||
if (shouldIgnoreSlot(slotId)) return RESULT_FIRST_USER
|
||||
|
||||
|
@ -212,7 +250,7 @@ class OpenEuiccService : EuiccService(), OpenEuiccContextMarker {
|
|||
portIndex: Int,
|
||||
iccid: String?,
|
||||
forceDeactivateSim: Boolean
|
||||
): Int {
|
||||
): Int = withEuiccChannelManager {
|
||||
Log.i(TAG,"onSwitchToSubscriptionWithPort slotId=$slotId portIndex=$portIndex iccid=$iccid forceDeactivateSim=$forceDeactivateSim")
|
||||
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 {
|
||||
Log.i(TAG, "onUpdateSubscriptionNickname slotId=$slotId iccid=$iccid nickname=$nickname")
|
||||
if (shouldIgnoreSlot(slotId)) return RESULT_FIRST_USER
|
||||
val channel = findChannel(slotId) ?: return RESULT_FIRST_USER
|
||||
if (!channel.profileExists(iccid)) {
|
||||
return RESULT_FIRST_USER
|
||||
override fun onUpdateSubscriptionNickname(slotId: Int, iccid: String, nickname: String?): Int =
|
||||
withEuiccChannelManager {
|
||||
Log.i(
|
||||
TAG,
|
||||
"onUpdateSubscriptionNickname slotId=$slotId iccid=$iccid nickname=$nickname"
|
||||
)
|
||||
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")
|
||||
override fun onEraseSubscriptions(slotId: Int): Int {
|
||||
|
|
|
@ -96,14 +96,17 @@ class SlotMappingFragment: BaseMaterialDialogFragment(),
|
|||
withContext(Dispatchers.IO) {
|
||||
// Use the utility method from PrivilegedTelephonyUtils to ensure
|
||||
// unmapped ports have all profiles disabled
|
||||
telephonyManager.updateSimSlotMapping(euiccChannelManager, adapter.mappings)
|
||||
telephonyManager.updateSimSlotMapping(
|
||||
(requireActivity() as BaseEuiccAccessActivity).euiccChannelManager,
|
||||
adapter.mappings
|
||||
)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Toast.makeText(requireContext(), R.string.slot_mapping_failure, Toast.LENGTH_LONG).show()
|
||||
return@launch
|
||||
}
|
||||
Toast.makeText(requireContext(), R.string.slot_mapping_completed, Toast.LENGTH_LONG).show()
|
||||
euiccChannelManager.invalidate()
|
||||
(requireActivity() as BaseEuiccAccessActivity).euiccChannelManager.invalidate()
|
||||
requireActivity().finish()
|
||||
}
|
||||
}
|
||||
|
|
27
app/src/main/java/im/angry/openeuicc/util/PrivilegedUtils.kt
Normal file
27
app/src/main/java/im/angry/openeuicc/util/PrivilegedUtils.kt
Normal 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)
|
||||
}
|
Loading…
Reference in a new issue