Compare commits
2 commits
2ccfe02204
...
53fa754197
Author | SHA1 | Date | |
---|---|---|---|
|
53fa754197 | ||
|
4090418146 |
|
@ -1,24 +1,17 @@
|
||||||
package im.angry.openeuicc.core
|
package im.angry.openeuicc.core
|
||||||
|
|
||||||
|
import im.angry.openeuicc.util.*
|
||||||
import net.typeblog.lpac_jni.LocalProfileAssistant
|
import net.typeblog.lpac_jni.LocalProfileAssistant
|
||||||
|
|
||||||
// A custom type to avoid compatibility issues with UiccCardInfo / UiccPortInfo
|
|
||||||
data class EuiccChannelInfo(
|
|
||||||
val slotId: Int,
|
|
||||||
val cardId: Int,
|
|
||||||
val name: String,
|
|
||||||
val imei: String,
|
|
||||||
val removable: Boolean
|
|
||||||
)
|
|
||||||
|
|
||||||
abstract class EuiccChannel(
|
abstract class EuiccChannel(
|
||||||
info: EuiccChannelInfo
|
port: UiccPortInfoCompat
|
||||||
) {
|
) {
|
||||||
val slotId = info.slotId
|
val slotId = port.card.physicalSlotIndex // PHYSICAL slot
|
||||||
val cardId = info.cardId
|
val logicalSlotId = port.logicalSlotIndex
|
||||||
val name = info.name
|
val portId = port.portIndex
|
||||||
val imei = info.imei
|
val cardId = port.card.cardId
|
||||||
val removable = info.removable
|
val name = "SLOT ${port.card.physicalSlotIndex}:${port.portIndex}"
|
||||||
|
val removable = port.card.isRemovable
|
||||||
|
|
||||||
abstract val lpa: LocalProfileAssistant
|
abstract val lpa: LocalProfileAssistant
|
||||||
val valid: Boolean
|
val valid: Boolean
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
package im.angry.openeuicc.core
|
package im.angry.openeuicc.core
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.os.Handler
|
import android.os.Handler
|
||||||
import android.os.HandlerThread
|
import android.os.HandlerThread
|
||||||
|
@ -17,7 +16,6 @@ import java.lang.IllegalArgumentException
|
||||||
import kotlin.coroutines.resume
|
import kotlin.coroutines.resume
|
||||||
import kotlin.coroutines.suspendCoroutine
|
import kotlin.coroutines.suspendCoroutine
|
||||||
|
|
||||||
@SuppressLint("MissingPermission") // We rely on ARA-based privileges, not READ_PRIVILEGED_PHONE_STATE
|
|
||||||
open class EuiccChannelManager(protected val context: Context) {
|
open class EuiccChannelManager(protected val context: Context) {
|
||||||
companion object {
|
companion object {
|
||||||
const val TAG = "EuiccChannelManager"
|
const val TAG = "EuiccChannelManager"
|
||||||
|
@ -52,27 +50,32 @@ open class EuiccChannelManager(protected val context: Context) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected open fun tryOpenEuiccChannelPrivileged(uiccInfo: UiccCardInfoCompat, channelInfo: EuiccChannelInfo): EuiccChannel? {
|
protected open fun tryOpenEuiccChannelPrivileged(port: UiccPortInfoCompat): EuiccChannel? {
|
||||||
// No-op when unprivileged
|
// No-op when unprivileged
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
protected fun tryOpenEuiccChannelUnprivileged(uiccInfo: UiccCardInfoCompat, channelInfo: EuiccChannelInfo): EuiccChannel? {
|
protected fun tryOpenEuiccChannelUnprivileged(port: UiccPortInfoCompat): EuiccChannel? {
|
||||||
Log.i(TAG, "Trying OMAPI for slot ${uiccInfo.physicalSlotIndex}")
|
if (port.portIndex != 0) {
|
||||||
|
Log.w(TAG, "OMAPI channel attempted on non-zero portId, ignoring")
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.i(TAG, "Trying OMAPI for physical slot ${port.card.physicalSlotIndex}")
|
||||||
try {
|
try {
|
||||||
return OmapiChannel(seService!!, channelInfo)
|
return OmapiChannel(seService!!, port)
|
||||||
} catch (e: IllegalArgumentException) {
|
} catch (e: IllegalArgumentException) {
|
||||||
// Failed
|
// Failed
|
||||||
Log.w(TAG, "OMAPI APDU interface unavailable for slot ${uiccInfo.physicalSlotIndex}.")
|
Log.w(TAG, "OMAPI APDU interface unavailable for physical slot ${port.card.physicalSlotIndex}.")
|
||||||
}
|
}
|
||||||
|
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun tryOpenEuiccChannel(uiccInfo: UiccCardInfoCompat): EuiccChannel? {
|
private suspend fun tryOpenEuiccChannel(port: UiccPortInfoCompat): EuiccChannel? {
|
||||||
lock.withLock {
|
lock.withLock {
|
||||||
ensureSEService()
|
ensureSEService()
|
||||||
val existing = channels.find { it.slotId == uiccInfo.physicalSlotIndex }
|
val existing = channels.find { it.slotId == port.card.physicalSlotIndex && it.portId == port.portIndex }
|
||||||
if (existing != null) {
|
if (existing != null) {
|
||||||
if (existing.valid) {
|
if (existing.valid) {
|
||||||
return existing
|
return existing
|
||||||
|
@ -82,18 +85,10 @@ open class EuiccChannelManager(protected val context: Context) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val channelInfo = EuiccChannelInfo(
|
var euiccChannel: EuiccChannel? = tryOpenEuiccChannelPrivileged(port)
|
||||||
uiccInfo.physicalSlotIndex,
|
|
||||||
uiccInfo.cardId,
|
|
||||||
"SIM ${uiccInfo.physicalSlotIndex}",
|
|
||||||
tm.getImei(uiccInfo.physicalSlotIndex) ?: return null,
|
|
||||||
uiccInfo.isRemovable
|
|
||||||
)
|
|
||||||
|
|
||||||
var euiccChannel: EuiccChannel? = tryOpenEuiccChannelPrivileged(uiccInfo, channelInfo)
|
|
||||||
|
|
||||||
if (euiccChannel == null) {
|
if (euiccChannel == null) {
|
||||||
euiccChannel = tryOpenEuiccChannelUnprivileged(uiccInfo, channelInfo)
|
euiccChannel = tryOpenEuiccChannelUnprivileged(port)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (euiccChannel != null) {
|
if (euiccChannel != null) {
|
||||||
|
@ -104,16 +99,28 @@ open class EuiccChannelManager(protected val context: Context) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun findEuiccChannelBySlot(slotId: Int): EuiccChannel? {
|
fun findEuiccChannelBySlotBlocking(logicalSlotId: Int): EuiccChannel? =
|
||||||
return tm.uiccCardsInfoCompat.find { it.physicalSlotIndex == slotId }?.let {
|
runBlocking {
|
||||||
tryOpenEuiccChannel(it)
|
if (!checkPrivileges()) return@runBlocking null
|
||||||
}
|
withContext(Dispatchers.IO) {
|
||||||
}
|
for (card in tm.uiccCardsInfoCompat) {
|
||||||
|
for (port in card.ports) {
|
||||||
|
if (port.logicalSlotIndex == logicalSlotId) {
|
||||||
|
return@withContext tryOpenEuiccChannel(port)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun findEuiccChannelBySlotBlocking(slotId: Int): EuiccChannel? = runBlocking {
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun findEuiccChannelByPortBlocking(physicalSlotId: Int, portId: Int): EuiccChannel? = runBlocking {
|
||||||
if (!checkPrivileges()) return@runBlocking null
|
if (!checkPrivileges()) return@runBlocking null
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
findEuiccChannelBySlot(slotId)
|
tm.uiccCardsInfoCompat.find { it.physicalSlotIndex == physicalSlotId }?.let { card ->
|
||||||
|
card.ports.find { it.portIndex == portId }?.let { tryOpenEuiccChannel(it) }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -124,8 +131,10 @@ open class EuiccChannelManager(protected val context: Context) {
|
||||||
ensureSEService()
|
ensureSEService()
|
||||||
|
|
||||||
for (uiccInfo in tm.uiccCardsInfoCompat) {
|
for (uiccInfo in tm.uiccCardsInfoCompat) {
|
||||||
if (tryOpenEuiccChannel(uiccInfo) != null) {
|
for (port in uiccInfo.ports) {
|
||||||
Log.d(TAG, "Found eUICC on slot ${uiccInfo.physicalSlotIndex}")
|
if (tryOpenEuiccChannel(port) != null) {
|
||||||
|
Log.d(TAG, "Found eUICC on slot ${uiccInfo.physicalSlotIndex} port ${port.portIndex}")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ package im.angry.openeuicc.core
|
||||||
import android.se.omapi.Channel
|
import android.se.omapi.Channel
|
||||||
import android.se.omapi.SEService
|
import android.se.omapi.SEService
|
||||||
import android.se.omapi.Session
|
import android.se.omapi.Session
|
||||||
|
import im.angry.openeuicc.util.UiccPortInfoCompat
|
||||||
import net.typeblog.lpac_jni.ApduInterface
|
import net.typeblog.lpac_jni.ApduInterface
|
||||||
import net.typeblog.lpac_jni.LocalProfileAssistant
|
import net.typeblog.lpac_jni.LocalProfileAssistant
|
||||||
import net.typeblog.lpac_jni.impl.HttpInterfaceImpl
|
import net.typeblog.lpac_jni.impl.HttpInterfaceImpl
|
||||||
|
@ -10,13 +11,13 @@ import net.typeblog.lpac_jni.impl.LocalProfileAssistantImpl
|
||||||
|
|
||||||
class OmapiApduInterface(
|
class OmapiApduInterface(
|
||||||
private val service: SEService,
|
private val service: SEService,
|
||||||
private val info: EuiccChannelInfo
|
private val port: UiccPortInfoCompat
|
||||||
): ApduInterface {
|
): ApduInterface {
|
||||||
private lateinit var session: Session
|
private lateinit var session: Session
|
||||||
private lateinit var lastChannel: Channel
|
private lateinit var lastChannel: Channel
|
||||||
|
|
||||||
override fun connect() {
|
override fun connect() {
|
||||||
session = service.getUiccReader(info.slotId + 1).openSession()
|
session = service.getUiccReader(port.logicalSlotIndex + 1).openSession()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun disconnect() {
|
override fun disconnect() {
|
||||||
|
@ -50,9 +51,9 @@ class OmapiApduInterface(
|
||||||
|
|
||||||
class OmapiChannel(
|
class OmapiChannel(
|
||||||
service: SEService,
|
service: SEService,
|
||||||
info: EuiccChannelInfo,
|
port: UiccPortInfoCompat,
|
||||||
) : EuiccChannel(info) {
|
) : EuiccChannel(port) {
|
||||||
override val lpa: LocalProfileAssistant = LocalProfileAssistantImpl(
|
override val lpa: LocalProfileAssistant = LocalProfileAssistantImpl(
|
||||||
OmapiApduInterface(service, info),
|
OmapiApduInterface(service, port),
|
||||||
HttpInterfaceImpl())
|
HttpInterfaceImpl())
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,23 +8,26 @@ import im.angry.openeuicc.util.openEuiccApplication
|
||||||
|
|
||||||
interface EuiccFragmentMarker
|
interface EuiccFragmentMarker
|
||||||
|
|
||||||
fun <T> newInstanceEuicc(clazz: Class<T>, slotId: Int): T where T: Fragment, T: EuiccFragmentMarker {
|
fun <T> newInstanceEuicc(clazz: Class<T>, slotId: Int, portId: Int): T where T: Fragment, T: EuiccFragmentMarker {
|
||||||
val instance = clazz.newInstance()
|
val instance = clazz.newInstance()
|
||||||
instance.arguments = Bundle().apply {
|
instance.arguments = Bundle().apply {
|
||||||
putInt("slotId", slotId)
|
putInt("slotId", slotId)
|
||||||
|
putInt("portId", portId)
|
||||||
}
|
}
|
||||||
return instance
|
return instance
|
||||||
}
|
}
|
||||||
|
|
||||||
val <T> T.slotId: Int where T: Fragment, T: EuiccFragmentMarker
|
val <T> T.slotId: Int where T: Fragment, T: EuiccFragmentMarker
|
||||||
get() = requireArguments().getInt("slotId")
|
get() = requireArguments().getInt("slotId")
|
||||||
|
val <T> T.portId: Int where T: Fragment, T: EuiccFragmentMarker
|
||||||
|
get() = requireArguments().getInt("portId")
|
||||||
|
|
||||||
val <T> T.euiccChannelManager: EuiccChannelManager where T: Fragment, T: EuiccFragmentMarker
|
val <T> T.euiccChannelManager: EuiccChannelManager where T: Fragment, T: EuiccFragmentMarker
|
||||||
get() = openEuiccApplication.euiccChannelManager
|
get() = openEuiccApplication.euiccChannelManager
|
||||||
|
|
||||||
val <T> T.channel: EuiccChannel where T: Fragment, T: EuiccFragmentMarker
|
val <T> T.channel: EuiccChannel where T: Fragment, T: EuiccFragmentMarker
|
||||||
get() =
|
get() =
|
||||||
euiccChannelManager.findEuiccChannelBySlotBlocking(slotId)!!
|
euiccChannelManager.findEuiccChannelByPortBlocking(slotId, portId)!!
|
||||||
|
|
||||||
interface EuiccProfilesChangedListener {
|
interface EuiccProfilesChangedListener {
|
||||||
fun onEuiccProfilesChanged()
|
fun onEuiccProfilesChanged()
|
||||||
|
|
|
@ -30,8 +30,8 @@ class EuiccManagementFragment : Fragment(), EuiccFragmentMarker, EuiccProfilesCh
|
||||||
companion object {
|
companion object {
|
||||||
const val TAG = "EuiccManagementFragment"
|
const val TAG = "EuiccManagementFragment"
|
||||||
|
|
||||||
fun newInstance(slotId: Int): EuiccManagementFragment =
|
fun newInstance(slotId: Int, portId: Int): EuiccManagementFragment =
|
||||||
newInstanceEuicc(EuiccManagementFragment::class.java, slotId)
|
newInstanceEuicc(EuiccManagementFragment::class.java, slotId, portId)
|
||||||
}
|
}
|
||||||
|
|
||||||
private lateinit var swipeRefresh: SwipeRefreshLayout
|
private lateinit var swipeRefresh: SwipeRefreshLayout
|
||||||
|
@ -62,7 +62,7 @@ class EuiccManagementFragment : Fragment(), EuiccFragmentMarker, EuiccProfilesCh
|
||||||
LinearLayoutManager(view.context, LinearLayoutManager.VERTICAL, false)
|
LinearLayoutManager(view.context, LinearLayoutManager.VERTICAL, false)
|
||||||
|
|
||||||
fab.setOnClickListener {
|
fab.setOnClickListener {
|
||||||
ProfileDownloadFragment.newInstance(slotId)
|
ProfileDownloadFragment.newInstance(slotId, portId)
|
||||||
.show(childFragmentManager, ProfileDownloadFragment.TAG)
|
.show(childFragmentManager, ProfileDownloadFragment.TAG)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -195,12 +195,12 @@ class EuiccManagementFragment : Fragment(), EuiccFragmentMarker, EuiccProfilesCh
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
R.id.rename -> {
|
R.id.rename -> {
|
||||||
ProfileRenameFragment.newInstance(slotId, profile.iccid, profile.displayName)
|
ProfileRenameFragment.newInstance(slotId, portId, profile.iccid, profile.displayName)
|
||||||
.show(childFragmentManager, ProfileRenameFragment.TAG)
|
.show(childFragmentManager, ProfileRenameFragment.TAG)
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
R.id.delete -> {
|
R.id.delete -> {
|
||||||
ProfileDeleteFragment.newInstance(slotId, profile.iccid, profile.displayName)
|
ProfileDeleteFragment.newInstance(slotId, portId, profile.iccid, profile.displayName)
|
||||||
.show(childFragmentManager, ProfileDeleteFragment.TAG)
|
.show(childFragmentManager, ProfileDeleteFragment.TAG)
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
|
@ -88,7 +88,7 @@ open class MainActivity : AppCompatActivity() {
|
||||||
withContext(Dispatchers.Main) {
|
withContext(Dispatchers.Main) {
|
||||||
manager.knownChannels.forEach { channel ->
|
manager.knownChannels.forEach { channel ->
|
||||||
spinnerAdapter.add(channel.name)
|
spinnerAdapter.add(channel.name)
|
||||||
fragments.add(EuiccManagementFragment.newInstance(channel.slotId))
|
fragments.add(EuiccManagementFragment.newInstance(channel.slotId, channel.portId))
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fragments.isNotEmpty()) {
|
if (fragments.isNotEmpty()) {
|
||||||
|
|
|
@ -16,8 +16,8 @@ class ProfileDeleteFragment : DialogFragment(), EuiccFragmentMarker {
|
||||||
companion object {
|
companion object {
|
||||||
const val TAG = "ProfileDeleteFragment"
|
const val TAG = "ProfileDeleteFragment"
|
||||||
|
|
||||||
fun newInstance(slotId: Int, iccid: String, name: String): ProfileDeleteFragment {
|
fun newInstance(slotId: Int, portId: Int, iccid: String, name: String): ProfileDeleteFragment {
|
||||||
val instance = newInstanceEuicc(ProfileDeleteFragment::class.java, slotId)
|
val instance = newInstanceEuicc(ProfileDeleteFragment::class.java, slotId, portId)
|
||||||
instance.requireArguments().apply {
|
instance.requireArguments().apply {
|
||||||
putString("iccid", iccid)
|
putString("iccid", iccid)
|
||||||
putString("name", name)
|
putString("name", name)
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package im.angry.openeuicc.ui
|
package im.angry.openeuicc.ui
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
import android.app.Dialog
|
import android.app.Dialog
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.text.Editable
|
import android.text.Editable
|
||||||
|
@ -16,19 +17,20 @@ import com.google.android.material.textfield.TextInputLayout
|
||||||
import com.journeyapps.barcodescanner.ScanContract
|
import com.journeyapps.barcodescanner.ScanContract
|
||||||
import com.journeyapps.barcodescanner.ScanOptions
|
import com.journeyapps.barcodescanner.ScanOptions
|
||||||
import im.angry.openeuicc.common.R
|
import im.angry.openeuicc.common.R
|
||||||
|
import im.angry.openeuicc.util.openEuiccApplication
|
||||||
import im.angry.openeuicc.util.setWidthPercent
|
import im.angry.openeuicc.util.setWidthPercent
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import net.typeblog.lpac_jni.ProfileDownloadCallback
|
import net.typeblog.lpac_jni.ProfileDownloadCallback
|
||||||
import java.lang.Exception
|
import kotlin.Exception
|
||||||
|
|
||||||
class ProfileDownloadFragment : DialogFragment(), EuiccFragmentMarker, Toolbar.OnMenuItemClickListener {
|
class ProfileDownloadFragment : DialogFragment(), EuiccFragmentMarker, Toolbar.OnMenuItemClickListener {
|
||||||
companion object {
|
companion object {
|
||||||
const val TAG = "ProfileDownloadFragment"
|
const val TAG = "ProfileDownloadFragment"
|
||||||
|
|
||||||
fun newInstance(slotId: Int): ProfileDownloadFragment =
|
fun newInstance(slotId: Int, portId: Int): ProfileDownloadFragment =
|
||||||
newInstanceEuicc(ProfileDownloadFragment::class.java, slotId)
|
newInstanceEuicc(ProfileDownloadFragment::class.java, slotId, portId)
|
||||||
}
|
}
|
||||||
|
|
||||||
private lateinit var toolbar: Toolbar
|
private lateinit var toolbar: Toolbar
|
||||||
|
@ -105,9 +107,16 @@ class ProfileDownloadFragment : DialogFragment(), EuiccFragmentMarker, Toolbar.O
|
||||||
setWidthPercent(95)
|
setWidthPercent(95)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressLint("MissingPermission")
|
||||||
override fun onStart() {
|
override fun onStart() {
|
||||||
super.onStart()
|
super.onStart()
|
||||||
profileDownloadIMEI.editText!!.text = Editable.Factory.getInstance().newEditable(channel.imei)
|
profileDownloadIMEI.editText!!.text = Editable.Factory.getInstance().newEditable(
|
||||||
|
try {
|
||||||
|
openEuiccApplication.telephonyManager.getImei(channel.logicalSlotId)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
""
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
lifecycleScope.launch(Dispatchers.IO) {
|
lifecycleScope.launch(Dispatchers.IO) {
|
||||||
// Fetch remaining NVRAM
|
// Fetch remaining NVRAM
|
||||||
|
|
|
@ -25,8 +25,8 @@ class ProfileRenameFragment : DialogFragment(), EuiccFragmentMarker {
|
||||||
companion object {
|
companion object {
|
||||||
const val TAG = "ProfileRenameFragment"
|
const val TAG = "ProfileRenameFragment"
|
||||||
|
|
||||||
fun newInstance(slotId: Int, iccid: String, currentName: String): ProfileRenameFragment {
|
fun newInstance(slotId: Int, portId: Int, iccid: String, currentName: String): ProfileRenameFragment {
|
||||||
val instance = newInstanceEuicc(ProfileRenameFragment::class.java, slotId)
|
val instance = newInstanceEuicc(ProfileRenameFragment::class.java, slotId, portId)
|
||||||
instance.requireArguments().apply {
|
instance.requireArguments().apply {
|
||||||
putString("iccid", iccid)
|
putString("iccid", iccid)
|
||||||
putString("currentName", currentName)
|
putString("currentName", currentName)
|
||||||
|
|
|
@ -68,6 +68,14 @@ class UiccPortInfoCompat(private val _inner: Any?, val card: UiccCardInfoCompat)
|
||||||
} else {
|
} else {
|
||||||
0
|
0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val logicalSlotIndex: Int
|
||||||
|
get() =
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||||
|
inner.logicalSlotIndex
|
||||||
|
} else {
|
||||||
|
card.physicalSlotIndex // logical is the same as physical below TIRAMISU
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val TelephonyManager.uiccCardsInfoCompat: List<UiccCardInfoCompat>
|
val TelephonyManager.uiccCardsInfoCompat: List<UiccCardInfoCompat>
|
||||||
|
|
|
@ -57,6 +57,8 @@ android {
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
|
||||||
|
implementation 'androidx.recyclerview:recyclerview:1.3.2'
|
||||||
compileOnly project(':libs:hidden-apis-stub')
|
compileOnly project(':libs:hidden-apis-stub')
|
||||||
implementation project(':libs:hidden-apis-shim')
|
implementation project(':libs:hidden-apis-shim')
|
||||||
implementation project(':libs:lpac-jni')
|
implementation project(':libs:lpac-jni')
|
||||||
|
|
|
@ -10,21 +10,21 @@ import java.lang.IllegalArgumentException
|
||||||
class PrivilegedEuiccChannelManager(context: Context): EuiccChannelManager(context) {
|
class PrivilegedEuiccChannelManager(context: Context): EuiccChannelManager(context) {
|
||||||
override fun checkPrivileges() = true // TODO: Implement proper system app check
|
override fun checkPrivileges() = true // TODO: Implement proper system app check
|
||||||
|
|
||||||
override fun tryOpenEuiccChannelPrivileged(uiccInfo: UiccCardInfoCompat, channelInfo: EuiccChannelInfo): EuiccChannel? {
|
override fun tryOpenEuiccChannelPrivileged(port: UiccPortInfoCompat): EuiccChannel? {
|
||||||
if (uiccInfo.isRemovable) {
|
if (port.card.isRemovable) {
|
||||||
// Attempt unprivileged (OMAPI) before TelephonyManager
|
// Attempt unprivileged (OMAPI) before TelephonyManager
|
||||||
// but still try TelephonyManager in case OMAPI is broken
|
// but still try TelephonyManager in case OMAPI is broken
|
||||||
super.tryOpenEuiccChannelUnprivileged(uiccInfo, channelInfo)?.let { return it }
|
super.tryOpenEuiccChannelUnprivileged(port)?.let { return it }
|
||||||
}
|
}
|
||||||
|
|
||||||
if (uiccInfo.isEuicc) {
|
if (port.card.isEuicc) {
|
||||||
Log.i(TAG, "Trying TelephonyManager for slot ${uiccInfo.physicalSlotIndex}")
|
Log.i(TAG, "Trying TelephonyManager for slot ${port.card.physicalSlotIndex} port ${port.portIndex}")
|
||||||
// TODO: On Tiramisu, we should also connect all available "ports" for MEP support
|
// TODO: On Tiramisu, we should also connect all available "ports" for MEP support
|
||||||
try {
|
try {
|
||||||
return TelephonyManagerChannel(channelInfo, tm)
|
return TelephonyManagerChannel(port, tm)
|
||||||
} catch (e: IllegalArgumentException) {
|
} catch (e: IllegalArgumentException) {
|
||||||
// Failed
|
// Failed
|
||||||
Log.w(TAG, "TelephonyManager APDU interface unavailable for slot ${uiccInfo.physicalSlotIndex}, falling back")
|
Log.w(TAG, "TelephonyManager APDU interface unavailable for slot ${port.card.physicalSlotIndex} port ${port.portIndex}, falling back")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null
|
return null
|
||||||
|
|
|
@ -9,7 +9,7 @@ import net.typeblog.lpac_jni.impl.HttpInterfaceImpl
|
||||||
import net.typeblog.lpac_jni.impl.LocalProfileAssistantImpl
|
import net.typeblog.lpac_jni.impl.LocalProfileAssistantImpl
|
||||||
|
|
||||||
class TelephonyManagerApduInterface(
|
class TelephonyManagerApduInterface(
|
||||||
private val info: EuiccChannelInfo,
|
private val port: UiccPortInfoCompat,
|
||||||
private val tm: TelephonyManager
|
private val tm: TelephonyManager
|
||||||
): ApduInterface {
|
): ApduInterface {
|
||||||
private var lastChannel: Int = -1
|
private var lastChannel: Int = -1
|
||||||
|
@ -25,9 +25,9 @@ class TelephonyManagerApduInterface(
|
||||||
override fun logicalChannelOpen(aid: ByteArray): Int {
|
override fun logicalChannelOpen(aid: ByteArray): Int {
|
||||||
check(lastChannel == -1) { "Already initialized" }
|
check(lastChannel == -1) { "Already initialized" }
|
||||||
val hex = aid.encodeHex()
|
val hex = aid.encodeHex()
|
||||||
val channel = tm.iccOpenLogicalChannelBySlot(info.slotId, hex, 0)
|
val channel = tm.iccOpenLogicalChannelByPortCompat(port.card.physicalSlotIndex, port.portIndex, hex, 0)
|
||||||
if (channel.status != IccOpenLogicalChannelResponse.STATUS_NO_ERROR || channel.channel == IccOpenLogicalChannelResponse.INVALID_CHANNEL) {
|
if (channel.status != IccOpenLogicalChannelResponse.STATUS_NO_ERROR || channel.channel == IccOpenLogicalChannelResponse.INVALID_CHANNEL) {
|
||||||
throw IllegalArgumentException("Cannot open logical channel " + hex + " via TelephonManager on slot " + info.slotId);
|
throw IllegalArgumentException("Cannot open logical channel $hex via TelephonManager on slot ${port.card.physicalSlotIndex} port ${port.portIndex}");
|
||||||
}
|
}
|
||||||
lastChannel = channel.channel
|
lastChannel = channel.channel
|
||||||
return lastChannel
|
return lastChannel
|
||||||
|
@ -35,7 +35,7 @@ class TelephonyManagerApduInterface(
|
||||||
|
|
||||||
override fun logicalChannelClose(handle: Int) {
|
override fun logicalChannelClose(handle: Int) {
|
||||||
check(handle == lastChannel) { "Invalid channel handle " }
|
check(handle == lastChannel) { "Invalid channel handle " }
|
||||||
tm.iccCloseLogicalChannelBySlot(info.slotId, handle)
|
tm.iccCloseLogicalChannelByPortCompat(port.card.physicalSlotIndex, port.portIndex, handle)
|
||||||
lastChannel = -1
|
lastChannel = -1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -49,18 +49,18 @@ class TelephonyManagerApduInterface(
|
||||||
val p3 = tx[4].toUByte().toInt()
|
val p3 = tx[4].toUByte().toInt()
|
||||||
val p4 = tx.drop(5).toByteArray().encodeHex()
|
val p4 = tx.drop(5).toByteArray().encodeHex()
|
||||||
|
|
||||||
return tm.iccTransmitApduLogicalChannelBySlot(info.slotId, lastChannel,
|
return tm.iccTransmitApduLogicalChannelByPortCompat(port.card.physicalSlotIndex, port.portIndex, lastChannel,
|
||||||
cla, instruction, p1, p2, p3, p4)?.decodeHex() ?: byteArrayOf()
|
cla, instruction, p1, p2, p3, p4)?.decodeHex() ?: byteArrayOf()
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class TelephonyManagerChannel(
|
class TelephonyManagerChannel(
|
||||||
info: EuiccChannelInfo,
|
port: UiccPortInfoCompat,
|
||||||
private val tm: TelephonyManager
|
private val tm: TelephonyManager
|
||||||
) : EuiccChannel(info) {
|
) : EuiccChannel(port) {
|
||||||
override val lpa: LocalProfileAssistant = LocalProfileAssistantImpl(
|
override val lpa: LocalProfileAssistant = LocalProfileAssistantImpl(
|
||||||
TelephonyManagerApduInterface(info, tm),
|
TelephonyManagerApduInterface(port, tm),
|
||||||
HttpInterfaceImpl()
|
HttpInterfaceImpl()
|
||||||
)
|
)
|
||||||
}
|
}
|
|
@ -27,6 +27,10 @@ class PrivilegedMainActivity : MainActivity() {
|
||||||
finish()
|
finish()
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
R.id.slot_mapping -> {
|
||||||
|
SlotMappingFragment().show(supportFragmentManager, SlotMappingFragment.TAG)
|
||||||
|
true
|
||||||
|
}
|
||||||
else -> super.onOptionsItemSelected(item)
|
else -> super.onOptionsItemSelected(item)
|
||||||
}
|
}
|
||||||
}
|
}
|
159
app/src/main/java/im/angry/openeuicc/ui/SlotMappingFragment.kt
Normal file
159
app/src/main/java/im/angry/openeuicc/ui/SlotMappingFragment.kt
Normal file
|
@ -0,0 +1,159 @@
|
||||||
|
package im.angry.openeuicc.ui
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.telephony.TelephonyManager
|
||||||
|
import android.telephony.UiccSlotMapping
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.MenuItem
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.widget.AdapterView
|
||||||
|
import android.widget.AdapterView.OnItemSelectedListener
|
||||||
|
import android.widget.ArrayAdapter
|
||||||
|
import android.widget.Spinner
|
||||||
|
import android.widget.TextView
|
||||||
|
import androidx.appcompat.widget.Toolbar
|
||||||
|
import androidx.appcompat.widget.Toolbar.OnMenuItemClickListener
|
||||||
|
import androidx.fragment.app.DialogFragment
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import im.angry.openeuicc.OpenEuiccApplication
|
||||||
|
import im.angry.openeuicc.R
|
||||||
|
import im.angry.openeuicc.util.*
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
|
||||||
|
class SlotMappingFragment: DialogFragment(), OnMenuItemClickListener {
|
||||||
|
companion object {
|
||||||
|
const val TAG = "SlotMappingFragment"
|
||||||
|
}
|
||||||
|
|
||||||
|
private val tm: TelephonyManager by lazy {
|
||||||
|
(requireContext().applicationContext as OpenEuiccApplication).telephonyManager
|
||||||
|
}
|
||||||
|
|
||||||
|
private val ports: List<UiccPortInfoCompat> by lazy {
|
||||||
|
tm.uiccCardsInfoCompat.flatMap { it.ports }
|
||||||
|
}
|
||||||
|
|
||||||
|
private val portsDesc: List<String> by lazy {
|
||||||
|
ports.map { getString(R.string.slot_mapping_port, it.card.physicalSlotIndex, it.portIndex) }
|
||||||
|
}
|
||||||
|
|
||||||
|
private lateinit var toolbar: Toolbar
|
||||||
|
private lateinit var recyclerView: RecyclerView
|
||||||
|
private lateinit var adapter: SlotMappingAdapter
|
||||||
|
|
||||||
|
override fun onCreateView(
|
||||||
|
inflater: LayoutInflater,
|
||||||
|
container: ViewGroup?,
|
||||||
|
savedInstanceState: Bundle?
|
||||||
|
): View? {
|
||||||
|
val view = inflater.inflate(R.layout.fragment_slot_mapping, container, false)
|
||||||
|
toolbar = view.findViewById(R.id.toolbar)
|
||||||
|
toolbar.inflateMenu(R.menu.fragment_slot_mapping)
|
||||||
|
recyclerView = view.findViewById(R.id.mapping_list)
|
||||||
|
recyclerView.layoutManager =
|
||||||
|
LinearLayoutManager(view.context, LinearLayoutManager.VERTICAL, false)
|
||||||
|
return view
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
toolbar.title = getString(R.string.slot_mapping)
|
||||||
|
toolbar.setNavigationOnClickListener { dismiss() }
|
||||||
|
toolbar.setOnMenuItemClickListener(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onResume() {
|
||||||
|
super.onResume()
|
||||||
|
setWidthPercent(85)
|
||||||
|
init()
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("NotifyDataSetChanged")
|
||||||
|
private fun init() {
|
||||||
|
lifecycleScope.launch(Dispatchers.Main) {
|
||||||
|
val mapping = withContext(Dispatchers.IO) {
|
||||||
|
tm.simSlotMapping
|
||||||
|
}
|
||||||
|
|
||||||
|
adapter = SlotMappingAdapter(mapping.toMutableList().apply {
|
||||||
|
sortBy { it.logicalSlotIndex }
|
||||||
|
})
|
||||||
|
recyclerView.adapter = adapter
|
||||||
|
adapter.notifyDataSetChanged()
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun commit() {
|
||||||
|
lifecycleScope.launch(Dispatchers.Main) {
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
|
tm.simSlotMapping = adapter.mappings
|
||||||
|
}
|
||||||
|
openEuiccApplication.euiccChannelManager.invalidate()
|
||||||
|
requireActivity().finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onMenuItemClick(item: MenuItem?): Boolean =
|
||||||
|
when (item!!.itemId) {
|
||||||
|
R.id.ok -> {
|
||||||
|
commit()
|
||||||
|
true
|
||||||
|
}
|
||||||
|
else -> false
|
||||||
|
}
|
||||||
|
|
||||||
|
inner class ViewHolder(root: View): RecyclerView.ViewHolder(root), OnItemSelectedListener {
|
||||||
|
private val textViewLogicalSlot: TextView = root.findViewById(R.id.slot_mapping_logical_slot)
|
||||||
|
private val spinnerPorts: Spinner = root.findViewById(R.id.slot_mapping_ports)
|
||||||
|
|
||||||
|
init {
|
||||||
|
spinnerPorts.adapter = ArrayAdapter(requireContext(), im.angry.openeuicc.common.R.layout.spinner_item, portsDesc)
|
||||||
|
spinnerPorts.onItemSelectedListener = this
|
||||||
|
}
|
||||||
|
|
||||||
|
private lateinit var mappings: MutableList<UiccSlotMapping>
|
||||||
|
private var mappingId: Int = -1
|
||||||
|
|
||||||
|
fun attachView(mappings: MutableList<UiccSlotMapping>, mappingId: Int) {
|
||||||
|
this.mappings = mappings
|
||||||
|
this.mappingId = mappingId
|
||||||
|
|
||||||
|
textViewLogicalSlot.text = getString(R.string.slot_mapping_logical_slot, mappings[mappingId].logicalSlotIndex)
|
||||||
|
spinnerPorts.setSelection(ports.indexOfFirst {
|
||||||
|
it.card.physicalSlotIndex == mappings[mappingId].physicalSlotIndex
|
||||||
|
&& it.portIndex == mappings[mappingId].portIndex
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
|
||||||
|
check(this::mappings.isInitialized) { "mapping not assigned" }
|
||||||
|
mappings[mappingId] =
|
||||||
|
UiccSlotMapping(
|
||||||
|
ports[position].portIndex, ports[position].card.physicalSlotIndex, mappings[mappingId].logicalSlotIndex)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onNothingSelected(parent: AdapterView<*>?) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inner class SlotMappingAdapter(val mappings: MutableList<UiccSlotMapping>): RecyclerView.Adapter<ViewHolder>() {
|
||||||
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
||||||
|
val view = LayoutInflater.from(parent.context).inflate(R.layout.fragment_slot_mapping_item, parent, false)
|
||||||
|
return ViewHolder(view)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getItemCount(): Int = mappings.size
|
||||||
|
|
||||||
|
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||||
|
holder.attachView(mappings, position)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,7 +4,7 @@ import android.os.Build
|
||||||
import android.telephony.IccOpenLogicalChannelResponse
|
import android.telephony.IccOpenLogicalChannelResponse
|
||||||
import android.telephony.TelephonyManager
|
import android.telephony.TelephonyManager
|
||||||
|
|
||||||
// TODO: Usage of *byPort APIs will still break build in-tree on lower AOSP versions
|
// TODO: Usage of new APIs from T or later will still break build in-tree on lower AOSP versions
|
||||||
// Maybe older versions should simply include hidden-apis-shim when building?
|
// Maybe older versions should simply include hidden-apis-shim when building?
|
||||||
fun TelephonyManager.iccOpenLogicalChannelByPortCompat(
|
fun TelephonyManager.iccOpenLogicalChannelByPortCompat(
|
||||||
slotIndex: Int, portIndex: Int, aid: String?, p2: Int
|
slotIndex: Int, portIndex: Int, aid: String?, p2: Int
|
||||||
|
|
29
app/src/main/res/layout/fragment_slot_mapping.xml
Normal file
29
app/src/main/res/layout/fragment_slot_mapping.xml
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.Toolbar
|
||||||
|
android:id="@+id/toolbar"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:theme="@style/Theme.OpenEUICC"
|
||||||
|
android:background="?attr/colorPrimary"
|
||||||
|
android:elevation="4dp"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
app:layout_constraintLeft_toLeftOf="parent"
|
||||||
|
app:layout_constraintWidth_percent="1"
|
||||||
|
app:navigationIcon="?homeAsUpIndicator" />
|
||||||
|
|
||||||
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
android:id="@+id/mapping_list"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:paddingTop="6dp"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/toolbar"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"/>
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
25
app/src/main/res/layout/fragment_slot_mapping_item.xml
Normal file
25
app/src/main/res/layout/fragment_slot_mapping_item.xml
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="48sp"
|
||||||
|
android:gravity="center">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/slot_mapping_logical_slot"
|
||||||
|
android:textSize="14sp"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="32sp"
|
||||||
|
android:layout_marginEnd="10sp" />
|
||||||
|
|
||||||
|
<Spinner
|
||||||
|
android:id="@+id/slot_mapping_ports"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="32sp" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
|
@ -7,4 +7,8 @@
|
||||||
android:checkable="true"
|
android:checkable="true"
|
||||||
android:visible="false"
|
android:visible="false"
|
||||||
app:showAsAction="never" />
|
app:showAsAction="never" />
|
||||||
|
<item
|
||||||
|
android:id="@+id/slot_mapping"
|
||||||
|
android:title="@string/slot_mapping"
|
||||||
|
app:showAsAction="never" />
|
||||||
</menu>
|
</menu>
|
9
app/src/main/res/menu/fragment_slot_mapping.xml
Normal file
9
app/src/main/res/menu/fragment_slot_mapping.xml
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||||
|
<item
|
||||||
|
android:id="@+id/ok"
|
||||||
|
android:icon="@drawable/ic_check_black"
|
||||||
|
android:title="@string/slot_mapping"
|
||||||
|
app:showAsAction="ifRoom"/>
|
||||||
|
</menu>
|
|
@ -5,4 +5,8 @@
|
||||||
<string name="dsds">Dual SIM</string>
|
<string name="dsds">Dual SIM</string>
|
||||||
|
|
||||||
<string name="toast_dsds_switched">DSDS state switched. Please wait until the modem restarts.</string>
|
<string name="toast_dsds_switched">DSDS state switched. Please wait until the modem restarts.</string>
|
||||||
|
|
||||||
|
<string name="slot_mapping">Slot Mapping</string>
|
||||||
|
<string name="slot_mapping_logical_slot">Logical slot %d:</string>
|
||||||
|
<string name="slot_mapping_port">Slot %d Port %d</string>
|
||||||
</resources>
|
</resources>
|
|
@ -31,7 +31,7 @@ android {
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
compileOnly project(':libs:hidden-apis-stub')
|
||||||
implementation 'androidx.core:core-ktx:1.7.0'
|
implementation 'androidx.core:core-ktx:1.7.0'
|
||||||
implementation 'androidx.appcompat:appcompat:1.4.2'
|
implementation 'androidx.appcompat:appcompat:1.4.2'
|
||||||
implementation 'com.google.android.material:material:1.6.1'
|
implementation 'com.google.android.material:material:1.6.1'
|
||||||
|
|
|
@ -3,6 +3,7 @@ package im.angry.openeuicc.util
|
||||||
import android.telephony.IccOpenLogicalChannelResponse
|
import android.telephony.IccOpenLogicalChannelResponse
|
||||||
import android.telephony.SubscriptionManager
|
import android.telephony.SubscriptionManager
|
||||||
import android.telephony.TelephonyManager
|
import android.telephony.TelephonyManager
|
||||||
|
import android.telephony.UiccSlotMapping
|
||||||
import java.lang.reflect.Method
|
import java.lang.reflect.Method
|
||||||
|
|
||||||
// Hidden APIs via reflection to enable building without AOSP source tree
|
// Hidden APIs via reflection to enable building without AOSP source tree
|
||||||
|
@ -46,6 +47,17 @@ private val iccTransmitApduLogicalChannelByPort: Method by lazy {
|
||||||
Int::class.java, Int::class.java, Int::class.java, String::class.java
|
Int::class.java, Int::class.java, Int::class.java, String::class.java
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
private val getSimSlotMapping: Method by lazy {
|
||||||
|
TelephonyManager::class.java.getMethod(
|
||||||
|
"getSimSlotMapping"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
private val setSimSlotMapping: Method by lazy {
|
||||||
|
TelephonyManager::class.java.getMethod(
|
||||||
|
"setSimSlotMapping",
|
||||||
|
Collection::class.java
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
fun TelephonyManager.iccOpenLogicalChannelBySlot(
|
fun TelephonyManager.iccOpenLogicalChannelBySlot(
|
||||||
slotId: Int, appletId: String?, p2: Int
|
slotId: Int, appletId: String?, p2: Int
|
||||||
|
@ -79,6 +91,10 @@ fun TelephonyManager.iccTransmitApduLogicalChannelByPort(
|
||||||
this, slotId, portId, channel, cla, instruction, p1, p2, p3, data
|
this, slotId, portId, channel, cla, instruction, p1, p2, p3, data
|
||||||
) as String?
|
) as String?
|
||||||
|
|
||||||
|
var TelephonyManager.simSlotMapping: Collection<UiccSlotMapping>
|
||||||
|
get() = getSimSlotMapping.invoke(this) as Collection<UiccSlotMapping>
|
||||||
|
set(new) { setSimSlotMapping.invoke(this, new) }
|
||||||
|
|
||||||
private val requestEmbeddedSubscriptionInfoListRefresh: Method by lazy {
|
private val requestEmbeddedSubscriptionInfoListRefresh: Method by lazy {
|
||||||
SubscriptionManager::class.java.getMethod("requestEmbeddedSubscriptionInfoListRefresh", Int::class.java)
|
SubscriptionManager::class.java.getMethod("requestEmbeddedSubscriptionInfoListRefresh", Int::class.java)
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,73 @@
|
||||||
|
package android.telephony;
|
||||||
|
|
||||||
|
import android.os.Parcel;
|
||||||
|
import android.os.Parcelable;
|
||||||
|
|
||||||
|
public final class UiccSlotMapping implements Parcelable {
|
||||||
|
public static final Creator<UiccSlotMapping> CREATOR = null;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void writeToParcel(Parcel dest, int flags) {
|
||||||
|
throw new RuntimeException("stub");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int describeContents() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param portIndex The port index is an enumeration of the ports available on the UICC.
|
||||||
|
* @param physicalSlotIndex is unique index referring to a physical SIM slot.
|
||||||
|
* @param logicalSlotIndex is unique index referring to a logical SIM slot.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public UiccSlotMapping(int portIndex, int physicalSlotIndex, int logicalSlotIndex) {
|
||||||
|
throw new RuntimeException("stub");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Port index is the unique index referring to a port belonging to the physical SIM slot.
|
||||||
|
* If the SIM does not support multiple enabled profiles, the port index is default index 0.
|
||||||
|
*
|
||||||
|
* @return port index.
|
||||||
|
*/
|
||||||
|
public int getPortIndex() {
|
||||||
|
throw new RuntimeException("stub");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the physical slot index for the slot that the UICC is currently inserted in.
|
||||||
|
*
|
||||||
|
* @return physical slot index which is the index of actual physical UICC slot.
|
||||||
|
*/
|
||||||
|
public int getPhysicalSlotIndex() {
|
||||||
|
throw new RuntimeException("stub");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets logical slot index for the slot that the UICC is currently attached.
|
||||||
|
* Logical slot index is the unique index referring to a logical slot(logical modem stack).
|
||||||
|
*
|
||||||
|
* @return logical slot index;
|
||||||
|
*/
|
||||||
|
public int getLogicalSlotIndex() {
|
||||||
|
throw new RuntimeException("stub");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
throw new RuntimeException("stub");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
throw new RuntimeException("stub");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
throw new RuntimeException("stub");
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue