Merge branch 'master' into refresh-after-switch
This commit is contained in:
commit
e69fcc8d17
17 changed files with 376 additions and 250 deletions
|
@ -9,6 +9,7 @@ import kotlinx.coroutines.flow.Flow
|
|||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import net.typeblog.lpac_jni.ApduInterface
|
||||
import java.util.concurrent.atomic.AtomicInteger
|
||||
|
||||
class OmapiApduInterface(
|
||||
private val service: SEService,
|
||||
|
@ -20,12 +21,8 @@ class OmapiApduInterface(
|
|||
}
|
||||
|
||||
private lateinit var session: Session
|
||||
private val channels = arrayOf<Channel?>(
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
)
|
||||
private val index = AtomicInteger(0)
|
||||
private val channels = mutableMapOf<Int, Channel>()
|
||||
|
||||
override val valid: Boolean
|
||||
get() = service.isConnected && (this::session.isInitialized && !session.isClosed)
|
||||
|
@ -44,21 +41,20 @@ class OmapiApduInterface(
|
|||
override fun logicalChannelOpen(aid: ByteArray): Int {
|
||||
val channel = session.openLogicalChannel(aid)
|
||||
check(channel != null) { "Failed to open logical channel (${aid.encodeHex()})" }
|
||||
val index = channels.indexOf(null)
|
||||
check(index != -1) { "No free logical channel slots" }
|
||||
synchronized(channels) { channels[index] = channel }
|
||||
return index
|
||||
val handle = index.incrementAndGet()
|
||||
synchronized(channels) { channels[handle] = channel }
|
||||
return handle
|
||||
}
|
||||
|
||||
override fun logicalChannelClose(handle: Int) {
|
||||
val channel = channels.getOrNull(handle)
|
||||
val channel = channels[handle]
|
||||
check(channel != null) { "Invalid logical channel handle $handle" }
|
||||
if (channel.isOpen) channel.close()
|
||||
synchronized(channels) { channels[handle] = null }
|
||||
synchronized(channels) { channels.remove(handle) }
|
||||
}
|
||||
|
||||
override fun transmit(handle: Int, tx: ByteArray): ByteArray {
|
||||
val channel = channels.getOrNull(handle)
|
||||
val channel = channels[handle]
|
||||
check(channel != null) { "Invalid logical channel handle $handle" }
|
||||
|
||||
if (runBlocking { verboseLoggingFlow.first() }) {
|
||||
|
|
|
@ -23,8 +23,6 @@ import im.angry.openeuicc.common.R
|
|||
import im.angry.openeuicc.core.EuiccChannel
|
||||
import im.angry.openeuicc.core.EuiccChannelManager
|
||||
import im.angry.openeuicc.util.*
|
||||
import im.angry.openeuicc.vendored.getESTKmeInfo
|
||||
import im.angry.openeuicc.vendored.getSIMLinkVersion
|
||||
import kotlinx.coroutines.launch
|
||||
import net.typeblog.lpac_jni.impl.PKID_GSMA_LIVE_CI
|
||||
import net.typeblog.lpac_jni.impl.PKID_GSMA_TEST_CI
|
||||
|
@ -104,14 +102,11 @@ class EuiccInfoActivity : BaseEuiccAccessActivity(), OpenEuiccContextMarker {
|
|||
add(Item(R.string.euicc_info_access_mode, channel.type))
|
||||
add(Item(R.string.euicc_info_removable, formatByBoolean(channel.port.card.isRemovable, YES_NO)))
|
||||
add(Item(R.string.euicc_info_eid, channel.lpa.eID, copiedToastResId = R.string.toast_eid_copied))
|
||||
getESTKmeInfo(channel.apduInterface)?.let {
|
||||
add(Item(R.string.euicc_info_sku, it.skuName))
|
||||
add(Item(R.string.euicc_info_sn, it.serialNumber, copiedToastResId = R.string.toast_sn_copied))
|
||||
add(Item(R.string.euicc_info_bl_ver, it.bootloaderVersion))
|
||||
add(Item(R.string.euicc_info_fw_ver, it.firmwareVersion))
|
||||
}
|
||||
getSIMLinkVersion(channel.lpa.eID, channel.lpa.euiccInfo2?.euiccFirmwareVersion)?.let {
|
||||
add(Item(R.string.euicc_info_sku, "9eSIM $it"))
|
||||
channel.tryParseEuiccVendorInfo()?.let { vendorInfo ->
|
||||
vendorInfo.skuName?.let { add(Item(R.string.euicc_info_sku, it)) }
|
||||
vendorInfo.serialNumber?.let { add(Item(R.string.euicc_info_sn, it)) }
|
||||
vendorInfo.firmwareVersion?.let { add(Item(R.string.euicc_info_fw_ver, it)) }
|
||||
vendorInfo.bootloaderVersion?.let { add(Item(R.string.euicc_info_bl_ver, it)) }
|
||||
}
|
||||
channel.lpa.euiccInfo2.let { info ->
|
||||
add(Item(R.string.euicc_info_sgp22_version, info?.sgp22Version.toString()))
|
||||
|
|
|
@ -20,13 +20,10 @@ class ProfileDeleteFragment : DialogFragment(), EuiccChannelFragmentMarker {
|
|||
private const val FIELD_ICCID = "iccid"
|
||||
private const val FIELD_NAME = "name"
|
||||
|
||||
fun newInstance(slotId: Int, portId: Int, iccid: String, name: String): ProfileDeleteFragment {
|
||||
val instance = newInstanceEuicc(ProfileDeleteFragment::class.java, slotId, portId)
|
||||
instance.requireArguments().apply {
|
||||
fun newInstance(slotId: Int, portId: Int, iccid: String, name: String) =
|
||||
newInstanceEuicc(ProfileDeleteFragment::class.java, slotId, portId) {
|
||||
putString(FIELD_ICCID, iccid)
|
||||
putString(FIELD_NAME, name)
|
||||
}
|
||||
return instance
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -91,19 +88,12 @@ class ProfileDeleteFragment : DialogFragment(), EuiccChannelFragmentMarker {
|
|||
requireParentFragment().lifecycleScope.launch {
|
||||
ensureEuiccChannelManager()
|
||||
euiccChannelManagerService.waitForForegroundTask()
|
||||
euiccChannelManagerService.launchProfileDeleteTask(slotId, portId, iccid).onStart {
|
||||
if (parentFragment is EuiccProfilesChangedListener) {
|
||||
// Trigger a refresh in the parent fragment -- it should wait until
|
||||
// any foreground task is completed before actually doing a refresh
|
||||
(parentFragment as EuiccProfilesChangedListener).onEuiccProfilesChanged()
|
||||
euiccChannelManagerService.launchProfileDeleteTask(slotId, portId, iccid)
|
||||
.onStart {
|
||||
parentFragment?.notifyEuiccProfilesChanged()
|
||||
runCatching(::dismiss)
|
||||
}
|
||||
|
||||
try {
|
||||
dismiss()
|
||||
} catch (e: IllegalStateException) {
|
||||
// Ignored
|
||||
}
|
||||
}.waitDone()
|
||||
.waitDone()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -7,6 +7,7 @@ import android.view.View
|
|||
import android.view.ViewGroup
|
||||
import android.widget.ProgressBar
|
||||
import android.widget.Toast
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.appcompat.widget.Toolbar
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.google.android.material.textfield.TextInputLayout
|
||||
|
@ -18,16 +19,16 @@ import net.typeblog.lpac_jni.LocalProfileAssistant
|
|||
|
||||
class ProfileRenameFragment : BaseMaterialDialogFragment(), EuiccChannelFragmentMarker {
|
||||
companion object {
|
||||
private const val FIELD_ICCID = "iccid"
|
||||
private const val FIELD_CURRENT_NAME = "currentName"
|
||||
|
||||
const val TAG = "ProfileRenameFragment"
|
||||
|
||||
fun newInstance(slotId: Int, portId: Int, iccid: String, currentName: String): ProfileRenameFragment {
|
||||
val instance = newInstanceEuicc(ProfileRenameFragment::class.java, slotId, portId)
|
||||
instance.requireArguments().apply {
|
||||
putString("iccid", iccid)
|
||||
putString("currentName", currentName)
|
||||
fun newInstance(slotId: Int, portId: Int, iccid: String, currentName: String) =
|
||||
newInstanceEuicc(ProfileRenameFragment::class.java, slotId, portId) {
|
||||
putString(FIELD_ICCID, iccid)
|
||||
putString(FIELD_CURRENT_NAME, currentName)
|
||||
}
|
||||
return instance
|
||||
}
|
||||
}
|
||||
|
||||
private lateinit var toolbar: Toolbar
|
||||
|
@ -36,6 +37,14 @@ class ProfileRenameFragment : BaseMaterialDialogFragment(), EuiccChannelFragment
|
|||
|
||||
private var renaming = false
|
||||
|
||||
private val iccid: String by lazy {
|
||||
requireArguments().getString(FIELD_ICCID)!!
|
||||
}
|
||||
|
||||
private val currentName: String by lazy {
|
||||
requireArguments().getString(FIELD_CURRENT_NAME)!!
|
||||
}
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
|
@ -54,7 +63,7 @@ class ProfileRenameFragment : BaseMaterialDialogFragment(), EuiccChannelFragment
|
|||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
profileRenameNewName.editText!!.setText(requireArguments().getString("currentName"))
|
||||
profileRenameNewName.editText!!.setText(currentName)
|
||||
toolbar.apply {
|
||||
setTitle(R.string.rename)
|
||||
setNavigationOnClickListener {
|
||||
|
@ -78,12 +87,8 @@ class ProfileRenameFragment : BaseMaterialDialogFragment(), EuiccChannelFragment
|
|||
}
|
||||
}
|
||||
|
||||
private fun showErrorAndCancel(errorStrRes: Int) {
|
||||
Toast.makeText(
|
||||
requireContext(),
|
||||
errorStrRes,
|
||||
Toast.LENGTH_LONG
|
||||
).show()
|
||||
private fun showErrorAndCancel(@StringRes resId: Int) {
|
||||
Toast.makeText(requireContext(), resId, Toast.LENGTH_LONG).show()
|
||||
|
||||
renaming = false
|
||||
progress.visibility = View.GONE
|
||||
|
@ -94,17 +99,15 @@ class ProfileRenameFragment : BaseMaterialDialogFragment(), EuiccChannelFragment
|
|||
progress.isIndeterminate = true
|
||||
progress.visibility = View.VISIBLE
|
||||
|
||||
val newName = profileRenameNewName.editText!!.text.toString().trim()
|
||||
|
||||
lifecycleScope.launch {
|
||||
ensureEuiccChannelManager()
|
||||
euiccChannelManagerService.waitForForegroundTask()
|
||||
val res = euiccChannelManagerService.launchProfileRenameTask(
|
||||
slotId,
|
||||
portId,
|
||||
requireArguments().getString("iccid")!!,
|
||||
profileRenameNewName.editText!!.text.toString().trim()
|
||||
).waitDone()
|
||||
val response = euiccChannelManagerService
|
||||
.launchProfileRenameTask(slotId, portId, iccid, newName).waitDone()
|
||||
|
||||
when (res) {
|
||||
when (response) {
|
||||
is LocalProfileAssistant.ProfileNameTooLongException -> {
|
||||
showErrorAndCancel(R.string.profile_rename_too_long)
|
||||
}
|
||||
|
@ -118,15 +121,9 @@ class ProfileRenameFragment : BaseMaterialDialogFragment(), EuiccChannelFragment
|
|||
}
|
||||
|
||||
else -> {
|
||||
if (parentFragment is EuiccProfilesChangedListener) {
|
||||
(parentFragment as EuiccProfilesChangedListener).onEuiccProfilesChanged()
|
||||
}
|
||||
parentFragment?.notifyEuiccProfilesChanged()
|
||||
|
||||
try {
|
||||
dismiss()
|
||||
} catch (e: IllegalStateException) {
|
||||
// Ignored
|
||||
}
|
||||
runCatching(::dismiss)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package im.angry.openeuicc.ui.wizard
|
||||
|
||||
import android.app.assist.AssistContent
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import android.view.inputmethod.InputMethodManager
|
||||
|
@ -8,6 +9,7 @@ import android.widget.ProgressBar
|
|||
import android.widget.Toast
|
||||
import androidx.activity.OnBackPressedCallback
|
||||
import androidx.activity.enableEdgeToEdge
|
||||
import androidx.core.net.toUri
|
||||
import androidx.core.view.ViewCompat
|
||||
import androidx.core.view.WindowInsetsCompat
|
||||
import androidx.core.view.updatePadding
|
||||
|
@ -111,6 +113,21 @@ class DownloadWizardActivity: BaseEuiccAccessActivity() {
|
|||
}
|
||||
}
|
||||
|
||||
override fun onProvideAssistContent(outContent: AssistContent?) {
|
||||
super.onProvideAssistContent(outContent)
|
||||
outContent?.webUri = try {
|
||||
val activationCode = ActivationCode(
|
||||
state.smdp,
|
||||
state.matchingId,
|
||||
null,
|
||||
state.confirmationCode != null,
|
||||
)
|
||||
"LPA:$activationCode".toUri()
|
||||
} catch (_: Exception) {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
override fun onSaveInstanceState(outState: Bundle) {
|
||||
super.onSaveInstanceState(outState)
|
||||
outState.putString("currentStepFragmentClassName", state.currentStepFragmentClassName)
|
||||
|
|
|
@ -20,4 +20,15 @@ data class ActivationCode(
|
|||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
val parts = arrayOf(
|
||||
"1",
|
||||
address,
|
||||
matchingId ?: "",
|
||||
oid ?: "",
|
||||
if (confirmationCodeRequired) "1" else ""
|
||||
)
|
||||
return parts.joinToString("$").trimEnd('$')
|
||||
}
|
||||
}
|
|
@ -7,43 +7,65 @@ import im.angry.openeuicc.core.EuiccChannelManager
|
|||
import im.angry.openeuicc.service.EuiccChannelManagerService
|
||||
import im.angry.openeuicc.ui.BaseEuiccAccessActivity
|
||||
|
||||
interface EuiccChannelFragmentMarker: OpenEuiccContextMarker
|
||||
private const val FIELD_SLOT_ID = "slotId"
|
||||
private const val FIELD_PORT_ID = "portId"
|
||||
|
||||
interface EuiccChannelFragmentMarker : OpenEuiccContextMarker
|
||||
|
||||
private typealias BundleSetter = Bundle.() -> Unit
|
||||
|
||||
// We must use extension functions because there is no way to add bounds to the type of "self"
|
||||
// in the definition of an interface, so the only way is to limit where the extension functions
|
||||
// can be applied.
|
||||
fun <T> newInstanceEuicc(clazz: Class<T>, slotId: Int, portId: Int, addArguments: Bundle.() -> Unit = {}): T where T: Fragment, T: EuiccChannelFragmentMarker {
|
||||
val instance = clazz.newInstance()
|
||||
instance.arguments = Bundle().apply {
|
||||
putInt("slotId", slotId)
|
||||
putInt("portId", portId)
|
||||
addArguments()
|
||||
fun <T> newInstanceEuicc(clazz: Class<T>, slotId: Int, portId: Int, addArguments: BundleSetter = {}): T
|
||||
where T : Fragment, T : EuiccChannelFragmentMarker =
|
||||
clazz.getDeclaredConstructor().newInstance().apply {
|
||||
arguments = Bundle()
|
||||
arguments!!.putInt(FIELD_SLOT_ID, slotId)
|
||||
arguments!!.putInt(FIELD_PORT_ID, portId)
|
||||
arguments!!.addArguments()
|
||||
}
|
||||
return instance
|
||||
}
|
||||
|
||||
// Convenient methods to avoid using `channel` for these
|
||||
// `channel` requires that the channel actually exists in EuiccChannelManager, which is
|
||||
// not always the case during operations such as switching
|
||||
val <T> T.slotId: Int where T: Fragment, T: EuiccChannelFragmentMarker
|
||||
get() = requireArguments().getInt("slotId")
|
||||
val <T> T.portId: Int where T: Fragment, T: EuiccChannelFragmentMarker
|
||||
get() = requireArguments().getInt("portId")
|
||||
val <T> T.isUsb: Boolean where T: Fragment, T: EuiccChannelFragmentMarker
|
||||
get() = requireArguments().getInt("slotId") == EuiccChannelManager.USB_CHANNEL_ID
|
||||
val <T> T.slotId: Int
|
||||
where T : Fragment, T : EuiccChannelFragmentMarker
|
||||
get() = requireArguments().getInt(FIELD_SLOT_ID)
|
||||
val <T> T.portId: Int
|
||||
where T : Fragment, T : EuiccChannelFragmentMarker
|
||||
get() = requireArguments().getInt(FIELD_PORT_ID)
|
||||
val <T> T.isUsb: Boolean
|
||||
where T : Fragment, T : EuiccChannelFragmentMarker
|
||||
get() = slotId == EuiccChannelManager.USB_CHANNEL_ID
|
||||
|
||||
val <T> T.euiccChannelManager: EuiccChannelManager where T: Fragment, T: OpenEuiccContextMarker
|
||||
get() = (requireActivity() as BaseEuiccAccessActivity).euiccChannelManager
|
||||
val <T> T.euiccChannelManagerService: EuiccChannelManagerService where T: Fragment, T: OpenEuiccContextMarker
|
||||
get() = (requireActivity() as BaseEuiccAccessActivity).euiccChannelManagerService
|
||||
private fun <T> T.requireEuiccActivity(): BaseEuiccAccessActivity
|
||||
where T : Fragment, T : OpenEuiccContextMarker =
|
||||
requireActivity() as BaseEuiccAccessActivity
|
||||
|
||||
suspend fun <T, R> T.withEuiccChannel(fn: suspend (EuiccChannel) -> R): R where T : Fragment, T : EuiccChannelFragmentMarker {
|
||||
val <T> T.euiccChannelManager: EuiccChannelManager
|
||||
where T : Fragment, T : OpenEuiccContextMarker
|
||||
get() = requireEuiccActivity().euiccChannelManager
|
||||
|
||||
val <T> T.euiccChannelManagerService: EuiccChannelManagerService
|
||||
where T : Fragment, T : OpenEuiccContextMarker
|
||||
get() = requireEuiccActivity().euiccChannelManagerService
|
||||
|
||||
suspend fun <T, R> T.withEuiccChannel(fn: suspend (EuiccChannel) -> R): R
|
||||
where T : Fragment, T : EuiccChannelFragmentMarker {
|
||||
ensureEuiccChannelManager()
|
||||
return euiccChannelManager.withEuiccChannel(slotId, portId, fn)
|
||||
}
|
||||
|
||||
suspend fun <T> T.ensureEuiccChannelManager() where T: Fragment, T: OpenEuiccContextMarker =
|
||||
(requireActivity() as BaseEuiccAccessActivity).euiccChannelManagerLoaded.await()
|
||||
suspend fun <T> T.ensureEuiccChannelManager() where T : Fragment, T : OpenEuiccContextMarker =
|
||||
requireEuiccActivity().euiccChannelManagerLoaded.await()
|
||||
|
||||
fun <T> T.notifyEuiccProfilesChanged() where T : Fragment {
|
||||
if (this !is EuiccProfilesChangedListener) return
|
||||
// Trigger a refresh in the parent fragment -- it should wait until
|
||||
// any foreground task is completed before actually doing a refresh
|
||||
this.onEuiccProfilesChanged()
|
||||
}
|
||||
|
||||
interface EuiccProfilesChangedListener {
|
||||
fun onEuiccProfilesChanged()
|
||||
|
|
112
app-common/src/main/java/im/angry/openeuicc/util/Vendors.kt
Normal file
112
app-common/src/main/java/im/angry/openeuicc/util/Vendors.kt
Normal file
|
@ -0,0 +1,112 @@
|
|||
package im.angry.openeuicc.util
|
||||
|
||||
import android.util.Log
|
||||
import im.angry.openeuicc.core.ApduInterfaceAtrProvider
|
||||
import im.angry.openeuicc.core.EuiccChannel
|
||||
import net.typeblog.lpac_jni.Version
|
||||
|
||||
data class EuiccVendorInfo(
|
||||
val skuName: String?,
|
||||
val serialNumber: String?,
|
||||
val bootloaderVersion: String?,
|
||||
val firmwareVersion: String?,
|
||||
)
|
||||
|
||||
private val EUICC_VENDORS: Array<EuiccVendor> = arrayOf(EstkMe(), SimLink())
|
||||
|
||||
fun EuiccChannel.tryParseEuiccVendorInfo(): EuiccVendorInfo? {
|
||||
EUICC_VENDORS.forEach { vendor ->
|
||||
vendor.tryParseEuiccVendorInfo(this@tryParseEuiccVendorInfo)?.let {
|
||||
return it
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
interface EuiccVendor {
|
||||
fun tryParseEuiccVendorInfo(channel: EuiccChannel): EuiccVendorInfo?
|
||||
}
|
||||
|
||||
private class EstkMe : EuiccVendor {
|
||||
companion object {
|
||||
private val PRODUCT_AID = "A06573746B6D65FFFFFFFFFFFF6D6774".decodeHex()
|
||||
private val PRODUCT_ATR_FPR = "estk.me".encodeToByteArray()
|
||||
}
|
||||
|
||||
private fun checkAtr(channel: EuiccChannel): Boolean {
|
||||
val iface = channel.apduInterface
|
||||
if (iface !is ApduInterfaceAtrProvider) return false
|
||||
val atr = iface.atr ?: return false
|
||||
for (index in atr.indices) {
|
||||
if (atr.size - index < PRODUCT_ATR_FPR.size) break
|
||||
if (atr.sliceArray(index until index + PRODUCT_ATR_FPR.size)
|
||||
.contentEquals(PRODUCT_ATR_FPR)
|
||||
) return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
private fun decodeAsn1String(b: ByteArray): String? {
|
||||
if (b.size < 2) return null
|
||||
if (b[b.size - 2] != 0x90.toByte() || b[b.size - 1] != 0x00.toByte()) return null
|
||||
return b.sliceArray(0 until b.size - 2).decodeToString()
|
||||
}
|
||||
|
||||
override fun tryParseEuiccVendorInfo(channel: EuiccChannel): EuiccVendorInfo? {
|
||||
if (!checkAtr(channel)) return null
|
||||
|
||||
val iface = channel.apduInterface
|
||||
return try {
|
||||
iface.withLogicalChannel(PRODUCT_AID) { transmit ->
|
||||
fun invoke(p1: Byte) =
|
||||
decodeAsn1String(transmit(byteArrayOf(0x00, 0x00, p1, 0x00, 0x00)))
|
||||
EuiccVendorInfo(
|
||||
skuName = invoke(0x03),
|
||||
serialNumber = invoke(0x00),
|
||||
bootloaderVersion = invoke(0x01),
|
||||
firmwareVersion = invoke(0x02),
|
||||
)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.d(TAG, "Failed to get ESTKmeInfo", e)
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class SimLink : EuiccVendor {
|
||||
companion object {
|
||||
private val EID_PATTERN = Regex("^89044045(84|21)67274948")
|
||||
}
|
||||
|
||||
override fun tryParseEuiccVendorInfo(channel: EuiccChannel): EuiccVendorInfo? {
|
||||
val eid = channel.lpa.eID
|
||||
val version = channel.lpa.euiccInfo2?.euiccFirmwareVersion
|
||||
if (version == null || EID_PATTERN.find(eid, 0) == null) return null
|
||||
val versionName = when {
|
||||
// @formatter:off
|
||||
version >= Version(37, 1, 41) -> "v3.1 (beta 1)"
|
||||
version >= Version(36, 18, 5) -> "v3 (final)"
|
||||
version >= Version(36, 17, 39) -> "v3 (beta)"
|
||||
version >= Version(36, 17, 4) -> "v2s"
|
||||
version >= Version(36, 9, 3) -> "v2.1"
|
||||
version >= Version(36, 7, 2) -> "v2"
|
||||
// @formatter:on
|
||||
else -> null
|
||||
}
|
||||
|
||||
val skuName = if (versionName == null) {
|
||||
"9eSIM"
|
||||
} else {
|
||||
"9eSIM $versionName"
|
||||
}
|
||||
|
||||
return EuiccVendorInfo(
|
||||
skuName = skuName,
|
||||
serialNumber = null,
|
||||
bootloaderVersion = null,
|
||||
firmwareVersion = null
|
||||
)
|
||||
}
|
||||
}
|
|
@ -1,49 +0,0 @@
|
|||
package im.angry.openeuicc.vendored
|
||||
|
||||
import android.util.Log
|
||||
import im.angry.openeuicc.core.ApduInterfaceAtrProvider
|
||||
import im.angry.openeuicc.util.TAG
|
||||
import im.angry.openeuicc.util.decodeHex
|
||||
import net.typeblog.lpac_jni.ApduInterface
|
||||
|
||||
data class ESTKmeInfo(
|
||||
val serialNumber: String?,
|
||||
val bootloaderVersion: String?,
|
||||
val firmwareVersion: String?,
|
||||
val skuName: String?,
|
||||
)
|
||||
|
||||
fun isESTKmeATR(iface: ApduInterface): Boolean {
|
||||
if (iface !is ApduInterfaceAtrProvider) return false
|
||||
val atr = iface.atr ?: return false
|
||||
val fpr = "estk.me".encodeToByteArray()
|
||||
for (index in atr.indices) {
|
||||
if (atr.size - index < fpr.size) break
|
||||
if (atr.sliceArray(index until index + fpr.size).contentEquals(fpr)) return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
fun getESTKmeInfo(iface: ApduInterface): ESTKmeInfo? {
|
||||
if (!isESTKmeATR(iface)) return null
|
||||
fun decode(b: ByteArray): String? {
|
||||
if (b.size < 2) return null
|
||||
if (b[b.size - 2] != 0x90.toByte() || b[b.size - 1] != 0x00.toByte()) return null
|
||||
return b.sliceArray(0 until b.size - 2).decodeToString()
|
||||
}
|
||||
return try {
|
||||
iface.withLogicalChannel("A06573746B6D65FFFFFFFFFFFF6D6774".decodeHex()) { transmit ->
|
||||
fun invoke(p1: Byte) = decode(transmit(byteArrayOf(0x00, 0x00, p1, 0x00, 0x00)))
|
||||
ESTKmeInfo(
|
||||
invoke(0x00), // serial number
|
||||
invoke(0x01), // bootloader version
|
||||
invoke(0x02), // firmware version
|
||||
invoke(0x03), // sku name
|
||||
)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.d(TAG, "Failed to get ESTKmeInfo", e)
|
||||
null
|
||||
}
|
||||
}
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
package im.angry.openeuicc.vendored
|
||||
|
||||
import net.typeblog.lpac_jni.Version
|
||||
|
||||
private val prefix = Regex("^89044045(84|21)67274948") // SIMLink EID prefix
|
||||
|
||||
fun getSIMLinkVersion(eid: String, version: Version?): String? {
|
||||
if (version == null || prefix.find(eid, 0) == null) return null
|
||||
return when {
|
||||
// @formatter:off
|
||||
version >= Version(37, 1, 41) -> "v3.1 (beta 1)"
|
||||
version >= Version(36, 18, 5) -> "v3 (final)"
|
||||
version >= Version(36, 17, 39) -> "v3 (beta)"
|
||||
version >= Version(36, 17, 4) -> "v2s"
|
||||
version >= Version(36, 9, 3) -> "v2.1"
|
||||
version >= Version(36, 7, 2) -> "v2"
|
||||
// @formatter:on
|
||||
else -> null
|
||||
}
|
||||
}
|
|
@ -3,13 +3,17 @@
|
|||
<string name="no_euicc">このアプリでアクセスできるリムーバブル eUICC カードがデバイス上で検出されていません。互換性のあるカード挿入または USB リーダーを接続してください。</string>
|
||||
<string name="no_profile">この eSIM にはプロファイルがありません。</string>
|
||||
<string name="unknown">不明</string>
|
||||
<string name="information_unavailable">情報なし</string>
|
||||
<string name="information_unavailable">情報がありません</string>
|
||||
<string name="help">ヘルプ</string>
|
||||
<string name="reload">スロットを再読み込み</string>
|
||||
<string name="channel_name_format">論理スロット %d</string>
|
||||
<string name="enabled">有効済み</string>
|
||||
<string name="disabled">無効済み</string>
|
||||
<string name="provider">プロバイダー:</string>
|
||||
<string name="profile_class">クラス:</string>
|
||||
<string name="profile_class_testing">テスト中</string>
|
||||
<string name="profile_class_provisioning">プロビジョニング</string>
|
||||
<string name="profile_class_operational">稼働中</string>
|
||||
<string name="enable">有効化</string>
|
||||
<string name="disable">無効化</string>
|
||||
<string name="delete">削除</string>
|
||||
|
@ -17,8 +21,9 @@
|
|||
<string name="enable_disable_timeout">eSIM チップがプロファイルの切り替えの待機中にタイムアウトしました。これはデバイスのモデムファームウェアのバグの可能性があります。機内モードに切り替えるかアプリを再起動、デバイスを再起動してください。</string>
|
||||
<string name="switch_did_not_refresh">操作は成功しましたが、デバイスのモデムが更新を拒否しました。新しいプロファイルを使用するには機内モードに切り替えるか、再起動する必要があります。</string>
|
||||
<string name="toast_profile_enable_failed">新しい eSIM プロファイルに切り替えることができません。</string>
|
||||
<string name="toast_profile_delete_confirm_text_mismatched">入力した確認用テキストは一致していません</string>
|
||||
<string name="toast_profile_delete_confirm_text_mismatched">確認文字列が一致しません</string>
|
||||
<string name="toast_iccid_copied">ICCID をクリップボードにコピーしました</string>
|
||||
<string name="toast_sn_copied">シリアル番号をクリップボードにコピーしました</string>
|
||||
<string name="toast_eid_copied">EID をクリップボードにコピーしました</string>
|
||||
<string name="toast_atr_copied">ATR をクリップボードにコピーしました</string>
|
||||
<string name="usb_permission">USB の権限を許可</string>
|
||||
|
@ -40,13 +45,15 @@
|
|||
<string name="profile_download_imei">IMEI (オプション)</string>
|
||||
<string name="profile_download_low_nvram_title">ダウンロードに失敗する可能性があります</string>
|
||||
<string name="profile_download_low_nvram_message">残り容量が少ないため、ダウンロードに失敗する可能性があります。</string>
|
||||
<string name="profile_download_no_lpa_string">クリップボードに LPA コードが見つかりません</string>
|
||||
<string name="profile_download_incorrect_lpa_string">LPA コードを解析できません</string>
|
||||
<string name="profile_download_incorrect_lpa_string_message">クリップボードまたは QR コードの内容を LPA コードとして解析できません</string>
|
||||
<string name="profile_download_no_lpa_string">クリップボードに LPA コードがありません</string>
|
||||
<string name="profile_download_required_confirmation_code">確認コードが必要です</string>
|
||||
<string name="profile_download_required_confirmation_code_message">クリップボードからスキャンした QR コードまたは LPA コードに必要な確認コードを入力してください。</string>
|
||||
<string name="profile_download_incorrect_lpa_string">解析できません</string>
|
||||
<string name="profile_download_incorrect_lpa_string_message">QR コードまたはクリップボードの内容を LPA コードとして解析できませんでした。</string>
|
||||
<string name="download_wizard">ダウンロードウィザード</string>
|
||||
<string name="download_wizard_back">戻る</string>
|
||||
<string name="download_wizard_next">次へ</string>
|
||||
<string name="download_wizard_slot_removed">選択された SIM が取り外されました</string>
|
||||
<string name="download_wizard_slot_removed">選択した SIM が削除されました</string>
|
||||
<string name="download_wizard_slot_select">ダウンロードする eSIM を選択または確認:</string>
|
||||
<string name="download_wizard_slot_type">タイプ:</string>
|
||||
<string name="download_wizard_slot_type_removable">リムーバブル</string>
|
||||
|
@ -76,12 +83,12 @@
|
|||
<string name="download_wizard_diagnostics_last_apdu_response_fail">最終の APDU レスポンス (SIM) は失敗しました</string>
|
||||
<string name="download_wizard_diagnostics_last_apdu_exception">最終の APDU 例外:</string>
|
||||
<string name="download_wizard_diagnostics_save">保存</string>
|
||||
<string name="download_wizard_diagnostics_file_template">%s のエラー診断</string>
|
||||
<string name="logs_saved_message">ログは指定されたパスに保存しました。他のアプリにシェアしますか?</string>
|
||||
<string name="download_wizard_diagnostics_file_template">「%s」での診断</string>
|
||||
<string name="logs_saved_message">ログは共有したパスに保存されました。別のアプリで共有しますか?</string>
|
||||
<string name="profile_rename_new_name">新しいニックネーム</string>
|
||||
<string name="profile_rename_encoding_error">ニックネームを UTF-8 にエンコードできません</string>
|
||||
<string name="profile_rename_too_long">ニックネームは 64 文字以内にしてください</string>
|
||||
<string name="profile_rename_failure">ニックネームの変更で予期せぬエラーが発生しました</string>
|
||||
<string name="profile_rename_encoding_error">ニックネームを UTF-8 にエンコードできませんでした</string>
|
||||
<string name="profile_rename_too_long">ニックネームが 64 文字を超えています</string>
|
||||
<string name="profile_rename_failure">プロファイルの名前変更時に不明なエラーが発生しました</string>
|
||||
<string name="profile_delete_confirm">%s のプロファイルを削除してもよろしいですか?この操作は元に戻せません。</string>
|
||||
<string name="profile_delete_confirm_input">削除を確認するには「%s」を入力してください</string>
|
||||
<string name="profile_notifications">通知</string>
|
||||
|
@ -98,16 +105,20 @@
|
|||
<string name="euicc_info_activity_title">eUICC 情報 (%s)</string>
|
||||
<string name="euicc_info_access_mode">アクセスモード</string>
|
||||
<string name="euicc_info_removable">リムーバブル</string>
|
||||
<string name="euicc_info_sku">製品名</string>
|
||||
<string name="euicc_info_sn">製品シリアル番号</string>
|
||||
<string name="euicc_info_bl_ver">製品ブートローダーバージョン</string>
|
||||
<string name="euicc_info_fw_ver">製品ファームウェアバージョン</string>
|
||||
<string name="euicc_info_sgp22_version">SGP.22 バージョン</string>
|
||||
<string name="euicc_info_firmware_version">eUICC OS のバージョン</string>
|
||||
<string name="euicc_info_firmware_version">eUICC OS バージョン</string>
|
||||
<string name="euicc_info_globalplatform_version">グローバルプラットフォームのバージョン</string>
|
||||
<string name="euicc_info_sas_accreditation_number">SAS 認定番号</string>
|
||||
<string name="euicc_info_pp_version">Protected Profileのバージョン</string>
|
||||
<string name="euicc_info_pp_version">保護されたプロファイルのバージョン</string>
|
||||
<string name="euicc_info_free_nvram">NVRAM の空き容量 (eSIM プロファイルストレージ)</string>
|
||||
<string name="euicc_info_ci_type">証明書の発行者 (CI)</string>
|
||||
<string name="euicc_info_ci_gsma_live">GSMA プロダクション CI</string>
|
||||
<string name="euicc_info_ci_type">証明書発行者 (CI)</string>
|
||||
<string name="euicc_info_ci_gsma_live">GSMA ライブ CI</string>
|
||||
<string name="euicc_info_ci_gsma_test">GSMA テスト CI</string>
|
||||
<string name="euicc_info_ci_unknown">未知の eSIM CI</string>
|
||||
<string name="euicc_info_ci_unknown">不明な eSIM CI</string>
|
||||
<string name="yes">はい</string>
|
||||
<string name="no">いいえ</string>
|
||||
<string name="logs_save">保存</string>
|
||||
|
@ -116,34 +127,28 @@
|
|||
<string name="developer_options_enabled">あなたは開発者になりました!</string>
|
||||
<string name="pref_settings">設定</string>
|
||||
<string name="pref_notifications">通知</string>
|
||||
<string name="pref_notifications_desc">eSIM のプロファイル操作により、通信事業者に通知が送信されます。ここでは、どのタイプの通知を送信するのかを微調整できます。</string>
|
||||
<string name="pref_notifications_desc">eSIM のプロファイル操作により、通信事業者に通知が送信されます。必要に応じてこの動作を微調整できます。</string>
|
||||
<string name="pref_notifications_download">ダウンロード</string>
|
||||
<string name="pref_notifications_download_desc">プロファイルの<i>ダウンロード済み</i>の通知を送信します</string>
|
||||
<string name="pref_notifications_download_desc">プロファイルを<i>ダウンロード中</i>の通知を送信します</string>
|
||||
<string name="pref_notifications_delete">削除</string>
|
||||
<string name="pref_notifications_delete_desc">プロファイルの<i>削除済み</i>の通知を送信します</string>
|
||||
<string name="pref_notifications_switch">切り替え</string>
|
||||
<string name="pref_notifications_switch_desc">プロファイルの<i>切り替え済み</i>の通知を送信します\nこのタイプの通知は有効化しても必ず送信するとは限らないことに注意してください。</string>
|
||||
<string name="pref_notifications_delete_desc">プロファイルを<i>削除中</i>の通知を送信します</string>
|
||||
<string name="pref_notifications_switch">切り替え中</string>
|
||||
<string name="pref_notifications_switch_desc">プロファイルを<i>切り替え中</i>の通知を送信します\nこのタイプの通知は信頼できないことに注意してください。</string>
|
||||
<string name="pref_advanced">高度な設定</string>
|
||||
<string name="pref_advanced_disable_safeguard_removable_esim">有効なプロファイルの無効化と削除を許可する</string>
|
||||
<string name="pref_advanced_disable_safeguard_removable_esim">プロファイルの無効化と削除を許可</string>
|
||||
<string name="pref_advanced_disable_safeguard_removable_esim_desc">デフォルトでは、このアプリでデバイスに挿入された取り外し可能な eSIM の有効なプロファイルを無効化することを防いでいます。なぜなのかというと<i>時々</i>アクセスができなくなるからです。\nこのチェックボックスを ON にすることで、この保護機能を<i>解除</i>します。</string>
|
||||
<string name="pref_advanced_verbose_logging">詳細ログ</string>
|
||||
<string name="pref_advanced_verbose_logging_desc">詳細ログを有効化します。これには個人的な情報が含まれている可能性があります。この機能を ON にした後は、信頼できるユーザーとのみログを共有してください。</string>
|
||||
<string name="pref_advanced_language">言語</string>
|
||||
<string name="pref_advanced_language_desc">アプリの言語</string>
|
||||
<string name="pref_advanced_logs">ログ</string>
|
||||
<string name="pref_advanced_logs_desc">アプリの最新デバッグログを表示します</string>
|
||||
<string name="pref_developer">開発者オプション</string>
|
||||
<string name="pref_developer_unfiltered_profile_list">フィルタリングされていないプロファイル一覧を表示</string>
|
||||
<string name="pref_developer_unfiltered_profile_list_desc">非運用のプロファイルも含めます</string>
|
||||
<string name="pref_developer_ignore_tls_certificate">SM-DP+ TLS 証明書を無視する</string>
|
||||
<string name="pref_developer_ignore_tls_certificate_desc">SM-DP+ TLS 証明書を無視して任意の RSP を許可します</string>
|
||||
<string name="pref_developer_ignore_tls_certificate_desc">RSP サーバーで使用される TLS 証明書を受け入れます</string>
|
||||
<string name="pref_info">情報</string>
|
||||
<string name="pref_info_app_version">アプリバージョン</string>
|
||||
<string name="pref_info_source_code">ソースコード</string>
|
||||
<string name="pref_advanced_language">言語</string>
|
||||
<string name="pref_advanced_language_desc">アプリの言語を選択</string>
|
||||
<string name="pref_developer_unfiltered_profile_list">すべてのプロファイルを表示</string>
|
||||
<string name="pref_developer_unfiltered_profile_list_desc">プロダクション以外のプロファイルも表示する</string>
|
||||
<string name="profile_class">タイプ:</string>
|
||||
<string name="profile_class_testing">テスティング</string>
|
||||
<string name="profile_class_provisioning">準備中</string>
|
||||
<string name="profile_class_operational">動作中</string>
|
||||
<string name="profile_download_required_confirmation_code">要確認コード</string>
|
||||
<string name="profile_download_required_confirmation_code_message">スキャンされた QR コード、又はクリップボードからペーストされた LPA コードには、確認コードの必要が表示されています。</string>
|
||||
</resources>
|
||||
|
|
|
@ -3,4 +3,5 @@
|
|||
<locale android:name="en-US" />
|
||||
<locale android:name="ja" />
|
||||
<locale android:name="zh-CN" />
|
||||
<locale android:name="zh-TW" />
|
||||
</locale-config>
|
|
@ -1,7 +1,11 @@
|
|||
package im.angry.openeuicc.ui
|
||||
|
||||
import android.content.pm.PackageManager
|
||||
import android.provider.Settings
|
||||
import android.view.Menu
|
||||
import android.view.MenuInflater
|
||||
import android.view.MenuItem
|
||||
import android.widget.Toast
|
||||
import im.angry.easyeuicc.R
|
||||
import im.angry.openeuicc.util.SIMToolkit
|
||||
import im.angry.openeuicc.util.newInstanceEuicc
|
||||
|
@ -23,9 +27,29 @@ class UnprivilegedEuiccManagementFragment : EuiccManagementFragment() {
|
|||
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
|
||||
super.onCreateOptionsMenu(menu, inflater)
|
||||
inflater.inflate(R.menu.fragment_sim_toolkit, menu)
|
||||
}
|
||||
|
||||
override fun onPrepareOptionsMenu(menu: Menu) {
|
||||
super.onPrepareOptionsMenu(menu)
|
||||
menu.findItem(R.id.open_sim_toolkit).apply {
|
||||
isVisible = stk.isAvailable(slotId)
|
||||
intent = stk.intent(slotId)
|
||||
intent = stk[slotId]?.intent
|
||||
isVisible = intent != null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem) = when (item.itemId) {
|
||||
R.id.open_sim_toolkit -> {
|
||||
SIMToolkit.getDisabledPackageName(item.intent)?.also { packageName ->
|
||||
val label = requireContext().packageManager.getApplicationLabel(packageName)
|
||||
val message = getString(R.string.toast_prompt_to_enable_sim_toolkit, label)
|
||||
Toast.makeText(requireContext(), message, Toast.LENGTH_LONG).show()
|
||||
}
|
||||
super.onOptionsItemSelected(item) // handling intent
|
||||
}
|
||||
|
||||
else -> super.onOptionsItemSelected(item)
|
||||
}
|
||||
}
|
||||
|
||||
private fun PackageManager.getApplicationLabel(packageName: String): CharSequence =
|
||||
getApplicationLabel(getApplicationInfo(packageName, 0))
|
||||
|
|
|
@ -3,65 +3,84 @@ package im.angry.openeuicc.util
|
|||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.pm.ActivityInfo
|
||||
import android.content.pm.PackageManager
|
||||
import android.net.Uri
|
||||
import android.provider.Settings
|
||||
import androidx.annotation.ArrayRes
|
||||
import im.angry.easyeuicc.R
|
||||
import im.angry.openeuicc.core.EuiccChannelManager
|
||||
|
||||
class SIMToolkit(private val context: Context) {
|
||||
private val slotSelection = getComponentNames(R.array.sim_toolkit_slot_selection)
|
||||
|
||||
private val slots = buildMap {
|
||||
fun getComponentNames(@ArrayRes id: Int) = context.resources
|
||||
.getStringArray(id).mapNotNull(ComponentName::unflattenFromString)
|
||||
put(-1, getComponentNames(R.array.sim_toolkit_slot_selection))
|
||||
put(0, getComponentNames(R.array.sim_toolkit_slot_1))
|
||||
put(1, getComponentNames(R.array.sim_toolkit_slot_2))
|
||||
}
|
||||
|
||||
private val packageNames = buildSet {
|
||||
addAll(slotSelection.map { it.packageName })
|
||||
addAll(slots.values.flatten().map { it.packageName })
|
||||
operator fun get(slotId: Int): Slot? = when (slotId) {
|
||||
-1, EuiccChannelManager.USB_CHANNEL_ID -> null
|
||||
else -> Slot(context.packageManager, buildSet {
|
||||
addAll(slots.getOrDefault(slotId, emptySet()))
|
||||
addAll(slots.getOrDefault(-1, emptySet()))
|
||||
})
|
||||
}
|
||||
|
||||
private val activities = packageNames.flatMap(::getActivities).toSet()
|
||||
data class Slot(private val packageManager: PackageManager, private val components: Set<ComponentName>) {
|
||||
private val packageNames: Iterable<String>
|
||||
get() = components.map(ComponentName::getPackageName).toSet()
|
||||
.filter(packageManager::isInstalledApp)
|
||||
|
||||
private val launchIntent by lazy {
|
||||
packageNames.firstNotNullOfOrNull(::getLaunchIntent)
|
||||
}
|
||||
private val launchIntent: Intent?
|
||||
get() = packageNames.firstNotNullOfOrNull(packageManager::getLaunchIntentForPackage)
|
||||
|
||||
private fun getLaunchIntent(packageName: String) = try {
|
||||
val pm = context.packageManager
|
||||
pm.getLaunchIntentForPackage(packageName)
|
||||
} catch (_: PackageManager.NameNotFoundException) {
|
||||
null
|
||||
}
|
||||
private val activities: Iterable<ComponentName>
|
||||
get() = packageNames.flatMap(packageManager::getActivities)
|
||||
.filter(ActivityInfo::exported).map { ComponentName(it.packageName, it.name) }
|
||||
|
||||
private fun getActivities(packageName: String): List<ComponentName> {
|
||||
return try {
|
||||
val pm = context.packageManager
|
||||
val packageInfo = pm.getPackageInfo(packageName, PackageManager.GET_ACTIVITIES)
|
||||
val activities = packageInfo.activities
|
||||
if (activities.isNullOrEmpty()) return emptyList()
|
||||
activities.filter { it.exported }.map { ComponentName(it.packageName, it.name) }
|
||||
} catch (_: PackageManager.NameNotFoundException) {
|
||||
emptyList()
|
||||
private fun getActivityIntent(): Intent? {
|
||||
for (activity in activities) {
|
||||
if (!components.contains(activity)) continue
|
||||
if (isDisabledState(packageManager.getComponentEnabledSetting(activity))) continue
|
||||
return Intent.makeMainActivity(activity)
|
||||
}
|
||||
return launchIntent
|
||||
}
|
||||
}
|
||||
|
||||
private fun getComponentNames(@ArrayRes id: Int) =
|
||||
context.resources.getStringArray(id).mapNotNull(ComponentName::unflattenFromString)
|
||||
|
||||
fun isAvailable(slotId: Int) = when (slotId) {
|
||||
-1 -> false
|
||||
EuiccChannelManager.USB_CHANNEL_ID -> false
|
||||
else -> intent(slotId) != null
|
||||
}
|
||||
|
||||
fun intent(slotId: Int): Intent? {
|
||||
val components = slots.getOrDefault(slotId, emptySet()) + slotSelection
|
||||
val intent = Intent(Intent.ACTION_MAIN, null).apply {
|
||||
flags = Intent.FLAG_ACTIVITY_NEW_TASK
|
||||
component = components.find(activities::contains)
|
||||
addCategory(Intent.CATEGORY_LAUNCHER)
|
||||
private fun getDisabledPackageIntent(): Intent? {
|
||||
val disabledPackageName = packageNames
|
||||
.find { isDisabledState(packageManager.getApplicationEnabledSetting(it)) }
|
||||
?: return null
|
||||
val uri = Uri.fromParts("package", disabledPackageName, null)
|
||||
return Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, uri)
|
||||
}
|
||||
|
||||
val intent: Intent?
|
||||
get() = getActivityIntent() ?: getDisabledPackageIntent()
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun getDisabledPackageName(intent: Intent?): String? {
|
||||
if (intent?.action != Settings.ACTION_APPLICATION_DETAILS_SETTINGS) return null
|
||||
return intent.data?.schemeSpecificPart
|
||||
}
|
||||
return if (intent.component != null) intent else launchIntent
|
||||
}
|
||||
}
|
||||
|
||||
private fun isDisabledState(state: Int) = when (state) {
|
||||
PackageManager.COMPONENT_ENABLED_STATE_DISABLED -> true
|
||||
PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER -> true
|
||||
else -> false
|
||||
}
|
||||
|
||||
private fun PackageManager.isInstalledApp(packageName: String) = try {
|
||||
getPackageInfo(packageName, 0)
|
||||
true
|
||||
} catch (_: PackageManager.NameNotFoundException) {
|
||||
false
|
||||
}
|
||||
|
||||
private fun PackageManager.getActivities(packageName: String) =
|
||||
getPackageInfo(packageName, PackageManager.GET_ACTIVITIES).activities?.toList() ?: emptyList()
|
||||
|
|
|
@ -2,33 +2,36 @@
|
|||
<resources>
|
||||
<string name="compatibility_check">互換性のチェック</string>
|
||||
<string name="open_sim_toolkit">SIM ツールキットを開く</string>
|
||||
<!-- Settings -->
|
||||
<!-- Toast -->
|
||||
<string name="toast_ara_m_copied">ARA-M SHA-1 をクリップボードにコピーしました</string>
|
||||
<string name="toast_prompt_to_enable_sim_toolkit">「%s」アプリを有効化してください</string>
|
||||
<!-- Compatibility Check Descriptions -->
|
||||
<string name="compatibility_check_system_features">システムの機能</string>
|
||||
<string name="compatibility_check_system_features_desc">デバイスにリムーバブル eUICC カードの管理に必要なすべての機能が備わっているかどうか。例えば基本的な電話機能や OMAPI のサポートなど。</string>
|
||||
<string name="compatibility_check_system_features_no_telephony">使用しているデバイスには電話機能がありません。</string>
|
||||
<string name="compatibility_check_system_features_no_omapi">使用しているデバイスまたはシステムには OMAPI のサポートを宣言していません。これは、ハードウェアからのサポートが不足していることが原因の可能性があります。または、フラグが不足していることが原因の可能性もあります。OMAPI が実際にサポートされているかどうかを判断するには次の 2 つのチェック項目を参照してください。</string>
|
||||
<string name="compatibility_check_omapi_connectivity">OMAPI の接続</string>
|
||||
<string name="compatibility_check_omapi_connectivity_desc">使用しているデバイスは、OMAPI 経由で SIM カード上のセキュアエレメントへのアクセスを許可していますか?</string>
|
||||
<string name="compatibility_check_omapi_connectivity_desc">使用しているデバイスは、OMAPI 経由で SIM カード上のセキュアエレメントへのアクセスを許可しているか否や。</string>
|
||||
<string name="compatibility_check_omapi_connectivity_fail">OMAPI 経由で SIM カードのセキュアエレメントリーダーを検出できません。このデバイスに SIM を挿入していない場合は、SIM を挿入後にこのチェックを再試行してください。</string>
|
||||
<string name="compatibility_check_omapi_connectivity_partial_success_sim_number">セキュアエレメントアクセスが正常に検出されましたが、次の SIM スロットでのみ有効です: <b>SIM%s</b>.</string>
|
||||
<string name="compatibility_check_omapi_connectivity_partial_success_sim_number">セキュアエレメントのアクセスが正常に検出されましたが、次の SIM スロットでのみ有効です: <b>SIM%s</b></string>
|
||||
<string name="compatibility_check_isdr_channel">ISD-R チャネルアクセス</string>
|
||||
<string name="compatibility_check_isdr_channel_desc">使用しているデバイスは、OMAPI 経由で eSIM への ISD-R (管理) チャネルを開くことをサポートしていますか?</string>
|
||||
<string name="compatibility_check_isdr_channel_desc">使用しているデバイスは、OMAPI 経由で eSIM への ISD-R (管理) チャネルを開くことをサポートしているか否や。</string>
|
||||
<string name="compatibility_check_isdr_channel_desc_unknown">OMAPI 経由での ISD-R アクセスがサポートされているかどうかを確認できません。まだ SIM カードが挿入されていない場合は、挿入した状態で再試行してください (どの SIM カードでも構いません)。</string>
|
||||
<string name="compatibility_check_isdr_channel_desc_partial_fail">ISD-R への OMAPI アクセスは、次のスロットでのみ可能です: <b>SIM%s</b>.</string>
|
||||
<string name="compatibility_check_known_broken">既知の破損リストに掲載されていない</string>
|
||||
<string name="compatibility_check_isdr_channel_desc_partial_fail">ISD-R への OMAPI アクセスは、次のスロットでのみ可能です: <b>SIM%s</b></string>
|
||||
<string name="compatibility_check_known_broken">既知の破損リストの記載されていない</string>
|
||||
<string name="compatibility_check_known_broken_desc">取り外し可能な eSIM に関連するバグがデバイスに存在しないかを確認します。</string>
|
||||
<string name="compatibility_check_known_broken_fail">おっと…使用しているデバイスには、取り外し可能な eSIM へのアクセス時にバグが存在します。これは必ずしも全く機能しないことを意味するわけではありませんが、注意して進める必要があります。</string>
|
||||
<string name="compatibility_check_known_broken_fail">おっと...使用しているデバイスには、取り外し可能な eSIM へのアクセス時にバグが存在します。これは必ずしも全く機能しないことを意味するわけではありませんが、注意して進める必要があります。</string>
|
||||
<string name="compatibility_check_usb">USB カードリーダーのサポート</string>
|
||||
<string name="compatibility_check_usb_desc">使用しているデバイスは、USB カードリーダー経由の eSIM の管理をサポートしていますか?</string>
|
||||
<string name="compatibility_check_usb_desc">使用しているデバイスは、USB カードリーダー経由の eSIM の管理をサポートしているか否や。</string>
|
||||
<string name="compatibility_check_usb_ok">このデバイスの標準 USB CCID リーダーを介して eSIM を管理できます (ここで他のチェック項目に失敗した場合でも)。カードリーダーを挿入し、このアプリを開いてこの方法で eSIM を管理できます。</string>
|
||||
<string name="compatibility_check_usb_fail">使用しているデバイスは USB ホストとしての機能をサポートしていません。</string>
|
||||
<string name="compatibility_check_verdict">判定 (USB 以外)</string>
|
||||
<string name="compatibility_check_verdict_desc">これまでのすべてのチェック項目に基づいて、デバイスに挿入された取り外し可能な eSIM の管理と互換性がある可能性はどのくらいありますか?</string>
|
||||
<string name="compatibility_check_verdict_desc">これまでのすべてのチェック項目に基づいて、デバイスに挿入された取り外し可能な eSIM の管理と互換性がある可能性はどの程度かについて</string>
|
||||
<string name="compatibility_check_verdict_ok">このデバイスに挿入された取り外し可能な eSIM の使用および管理が使用できる可能性があります。</string>
|
||||
<string name="compatibility_check_verdict_known_broken">挿入された取り外し可能な eSIM にアクセスするとデバイスにバグが発生することが知られています。\n%s</string>
|
||||
<string name="compatibility_check_verdict_unknown_likely_ok">挿入された取り外し可能な eSIM が使用しているデバイスで管理できるかはわかりません。ただし、このデバイスは OMAPI のサポートを宣言しているため、動作する可能性はわずかに高くなります。\n%s</string>
|
||||
<string name="compatibility_check_verdict_unknown_likely_fail">挿入された取り外し可能な eSIM がデバイス上で管理できるかどうかは判断できません。デバイスが OMAPI のサポートを宣言していないため、このデバイス上で取り外し可能な eSIM を管理することはサポートされていない可能性があります。\n%s</string>
|
||||
<string name="compatibility_check_verdict_unknown">挿入された取り外し可能な eSIM がデバイス上で管理できるかどうかを確認できません。\n%s</string>
|
||||
<string name="compatibility_check_verdict_fail_shared">ただし、eSIM プロファイルがすでに読み込まれている場合、有効化されたプロファイル自体は引き続き機能します。また、プロファイルが管理できない場合は、このデバイスで USB カードリーダーを介してプロファイルを管理できる可能性があります。</string>
|
||||
<string name="toast_ara_m_copied">ARA-M SHA-1 をクリップボードにコピーしました</string>
|
||||
</resources>
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
|
||||
<!-- Toast -->
|
||||
<string name="toast_ara_m_copied">ARA-M SHA-1 copied to clipboard</string>
|
||||
<string name="toast_prompt_to_enable_sim_toolkit">Please ENABLE your \"%s\" application</string>
|
||||
|
||||
<!-- Compatibility Check Descriptions -->
|
||||
<string name="compatibility_check_system_features">System Features</string>
|
||||
|
|
|
@ -69,11 +69,13 @@ fun TelephonyManager.iccOpenLogicalChannelByPort(
|
|||
): IccOpenLogicalChannelResponse =
|
||||
iccOpenLogicalChannelByPort.invoke(this, slotId, portId, appletId, p2) as IccOpenLogicalChannelResponse
|
||||
|
||||
fun TelephonyManager.iccCloseLogicalChannelBySlot(slotId: Int, channel: Int): Boolean =
|
||||
iccCloseLogicalChannelBySlot.invoke(this, slotId, channel) as Boolean
|
||||
fun TelephonyManager.iccCloseLogicalChannelBySlot(slotId: Int, channel: Int) {
|
||||
iccCloseLogicalChannelBySlot.invoke(this, slotId, channel)
|
||||
}
|
||||
|
||||
fun TelephonyManager.iccCloseLogicalChannelByPort(slotId: Int, portId: Int, channel: Int): Boolean =
|
||||
iccCloseLogicalChannelByPort.invoke(this, slotId, portId, channel) as Boolean
|
||||
fun TelephonyManager.iccCloseLogicalChannelByPort(slotId: Int, portId: Int, channel: Int) {
|
||||
iccCloseLogicalChannelByPort.invoke(this, slotId, portId, channel)
|
||||
}
|
||||
|
||||
fun TelephonyManager.iccTransmitApduLogicalChannelBySlot(
|
||||
slotId: Int, channel: Int, cla: Int, instruction: Int,
|
||||
|
|
Loading…
Add table
Reference in a new issue