Compare commits

..

No commits in common. "99d9200c28f73afed57a227074e09c7bfa4629be" and "d5aefcaec740c7fd646912612eefbb49cee2ad12" have entirely different histories.

5 changed files with 57 additions and 94 deletions

View file

@ -9,7 +9,6 @@ import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.first
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import net.typeblog.lpac_jni.ApduInterface import net.typeblog.lpac_jni.ApduInterface
import java.util.concurrent.atomic.AtomicInteger
class OmapiApduInterface( class OmapiApduInterface(
private val service: SEService, private val service: SEService,
@ -21,8 +20,12 @@ class OmapiApduInterface(
} }
private lateinit var session: Session private lateinit var session: Session
private val index = AtomicInteger(0) private val channels = arrayOf<Channel?>(
private val channels = mutableMapOf<Int, Channel>() null,
null,
null,
null,
)
override val valid: Boolean override val valid: Boolean
get() = service.isConnected && (this::session.isInitialized && !session.isClosed) get() = service.isConnected && (this::session.isInitialized && !session.isClosed)
@ -41,20 +44,21 @@ class OmapiApduInterface(
override fun logicalChannelOpen(aid: ByteArray): Int { override fun logicalChannelOpen(aid: ByteArray): Int {
val channel = session.openLogicalChannel(aid) val channel = session.openLogicalChannel(aid)
check(channel != null) { "Failed to open logical channel (${aid.encodeHex()})" } check(channel != null) { "Failed to open logical channel (${aid.encodeHex()})" }
val handle = index.incrementAndGet() val index = channels.indexOf(null)
synchronized(channels) { channels[handle] = channel } check(index != -1) { "No free logical channel slots" }
return handle synchronized(channels) { channels[index] = channel }
return index
} }
override fun logicalChannelClose(handle: Int) { override fun logicalChannelClose(handle: Int) {
val channel = channels[handle] val channel = channels.getOrNull(handle)
check(channel != null) { "Invalid logical channel handle $handle" } check(channel != null) { "Invalid logical channel handle $handle" }
if (channel.isOpen) channel.close() if (channel.isOpen) channel.close()
synchronized(channels) { channels.remove(handle) } synchronized(channels) { channels[handle] = null }
} }
override fun transmit(handle: Int, tx: ByteArray): ByteArray { override fun transmit(handle: Int, tx: ByteArray): ByteArray {
val channel = channels[handle] val channel = channels.getOrNull(handle)
check(channel != null) { "Invalid logical channel handle $handle" } check(channel != null) { "Invalid logical channel handle $handle" }
if (runBlocking { verboseLoggingFlow.first() }) { if (runBlocking { verboseLoggingFlow.first() }) {

View file

@ -3,5 +3,4 @@
<locale android:name="en-US" /> <locale android:name="en-US" />
<locale android:name="ja" /> <locale android:name="ja" />
<locale android:name="zh-CN" /> <locale android:name="zh-CN" />
<locale android:name="zh-TW" />
</locale-config> </locale-config>

View file

@ -1,10 +1,7 @@
package im.angry.openeuicc.ui package im.angry.openeuicc.ui
import android.content.pm.PackageManager
import android.provider.Settings
import android.view.Menu import android.view.Menu
import android.view.MenuInflater import android.view.MenuInflater
import android.widget.Toast
import im.angry.easyeuicc.R import im.angry.easyeuicc.R
import im.angry.openeuicc.util.SIMToolkit import im.angry.openeuicc.util.SIMToolkit
import im.angry.openeuicc.util.newInstanceEuicc import im.angry.openeuicc.util.newInstanceEuicc
@ -27,22 +24,8 @@ class UnprivilegedEuiccManagementFragment : EuiccManagementFragment() {
super.onCreateOptionsMenu(menu, inflater) super.onCreateOptionsMenu(menu, inflater)
inflater.inflate(R.menu.fragment_sim_toolkit, menu) inflater.inflate(R.menu.fragment_sim_toolkit, menu)
menu.findItem(R.id.open_sim_toolkit).apply { menu.findItem(R.id.open_sim_toolkit).apply {
val slot = stk[slotId] ?: return@apply isVisible = stk.isAvailable(slotId)
isVisible = slot.intent != null intent = stk.intent(slotId)
setOnMenuItemClickListener {
val intent = slot.intent ?: return@setOnMenuItemClickListener false
if (intent.action == Settings.ACTION_APPLICATION_DETAILS_SETTINGS) {
val packageName = intent.data!!.schemeSpecificPart
val label = requireContext().packageManager.getApplicationLabel(packageName)
val message = requireContext().getString(R.string.toast_prompt_to_enable_sim_toolkit, label)
Toast.makeText(context, message, Toast.LENGTH_LONG).show()
}
startActivity(intent)
true
}
} }
} }
} }
private fun PackageManager.getApplicationLabel(packageName: String): CharSequence =
getApplicationLabel(getApplicationInfo(packageName, 0))

View file

@ -3,87 +3,65 @@ package im.angry.openeuicc.util
import android.content.ComponentName import android.content.ComponentName
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.pm.ActivityInfo
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.net.Uri
import android.provider.Settings
import android.widget.Toast
import androidx.annotation.ArrayRes import androidx.annotation.ArrayRes
import im.angry.easyeuicc.R import im.angry.easyeuicc.R
import im.angry.openeuicc.core.EuiccChannelManager import im.angry.openeuicc.core.EuiccChannelManager
class SIMToolkit(private val context: Context) { class SIMToolkit(private val context: Context) {
private val slotSelection = getComponentNames(R.array.sim_toolkit_slot_selection)
private val slots = buildMap { 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(0, getComponentNames(R.array.sim_toolkit_slot_1))
put(1, getComponentNames(R.array.sim_toolkit_slot_2)) put(1, getComponentNames(R.array.sim_toolkit_slot_2))
} }
operator fun get(slotId: Int): Slot? = when (slotId) { private val packageNames = buildSet {
-1, EuiccChannelManager.USB_CHANNEL_ID -> null addAll(slotSelection.map { it.packageName })
else -> Slot(context.packageManager, buildSet { addAll(slots.values.flatten().map { it.packageName })
addAll(slots.getOrDefault(slotId, emptySet()))
addAll(slots.getOrDefault(-1, emptySet()))
})
} }
data class Slot(private val packageManager: PackageManager, private val components: Set<ComponentName>) { private val activities = packageNames.flatMap(::getActivities).toSet()
private val packageNames: Iterable<String>
get() = components.map(ComponentName::getPackageName).toSet()
private val launchIntent: Intent? private val launchIntent by lazy {
get() = packageNames.firstNotNullOfOrNull(packageManager::getLaunchIntent) packageNames.firstNotNullOfOrNull(::getLaunchIntent)
}
private val activities: Iterable<ComponentName> private fun getLaunchIntent(packageName: String) = try {
get() = packageNames.flatMap(packageManager::getActivities) val pm = context.packageManager
.filter(ActivityInfo::exported).map { ComponentName(it.packageName, it.name) } pm.getLaunchIntentForPackage(packageName)
} catch (_: PackageManager.NameNotFoundException) {
null
}
private fun getActivityIntent(): Intent? { private fun getActivities(packageName: String): List<ComponentName> {
for (activity in activities) { return try {
if (!components.contains(activity)) continue val pm = context.packageManager
if (isDisabledState(packageManager.getComponentEnabledSetting(activity))) continue val packageInfo = pm.getPackageInfo(packageName, PackageManager.GET_ACTIVITIES)
return Intent.makeMainActivity(activity) val activities = packageInfo.activities
} if (activities.isNullOrEmpty()) return emptyList()
return launchIntent activities.filter { it.exported }.map { ComponentName(it.packageName, it.name) }
} catch (_: PackageManager.NameNotFoundException) {
emptyList()
} }
}
private fun getDisabledPackageIntent(): Intent? { private fun getComponentNames(@ArrayRes id: Int) =
val disabledPackageName = packageNames.find { context.resources.getStringArray(id).mapNotNull(ComponentName::unflattenFromString)
try {
isDisabledState(packageManager.getApplicationEnabledSetting(it)) fun isAvailable(slotId: Int) = when (slotId) {
} catch (_: IllegalArgumentException) { -1 -> false
false EuiccChannelManager.USB_CHANNEL_ID -> false
} else -> intent(slotId) != null
} }
if (disabledPackageName == null) return null
return Intent( fun intent(slotId: Int): Intent? {
Settings.ACTION_APPLICATION_DETAILS_SETTINGS, val components = slots.getOrDefault(slotId, emptySet()) + slotSelection
Uri.fromParts("package", disabledPackageName, null) val intent = Intent(Intent.ACTION_MAIN, null).apply {
) flags = Intent.FLAG_ACTIVITY_NEW_TASK
component = components.find(activities::contains)
addCategory(Intent.CATEGORY_LAUNCHER)
} }
return if (intent.component != null) intent else launchIntent
val intent: Intent?
get() = getActivityIntent() ?: getDisabledPackageIntent()
} }
} }
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.getLaunchIntent(packageName: String) = try {
getLaunchIntentForPackage(packageName)
} catch (_: PackageManager.NameNotFoundException) {
null
}
private fun PackageManager.getActivities(packageName: String) = try {
getPackageInfo(packageName, PackageManager.GET_ACTIVITIES)
.activities?.toList() ?: emptyList()
} catch (_: PackageManager.NameNotFoundException) {
emptyList()
}

View file

@ -9,7 +9,6 @@
<!-- Toast --> <!-- Toast -->
<string name="toast_ara_m_copied">ARA-M SHA-1 copied to clipboard</string> <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 --> <!-- Compatibility Check Descriptions -->
<string name="compatibility_check_system_features">System Features</string> <string name="compatibility_check_system_features">System Features</string>