Compare commits

...

1 commit

Author SHA1 Message Date
b59a85e120
refactor: sim toolkit 2025-03-05 19:08:22 +08:00
3 changed files with 82 additions and 100 deletions

View file

@ -1,12 +1,7 @@
package im.angry.openeuicc.ui package im.angry.openeuicc.ui
import android.content.Intent
import android.content.pm.PackageManager
import android.net.Uri
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
@ -29,36 +24,9 @@ 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 {
isVisible = stk.isAvailable(slotId) val slot = stk[slotId] ?: return@apply
setOnMenuItemClickListener { launchSIMToolkit() } isVisible = slot.available
} setOnMenuItemClickListener { slot.launch() }
}
private fun launchSIMToolkit(): Boolean {
val intent = stk.intent(slotId)
val disabledPackageName = stk.getDisabledPackageName(slotId)
if (disabledPackageName == null && intent != null) {
startActivity(intent)
return true
} else if (disabledPackageName != null) {
val context = requireContext()
val appInfo = context.packageManager
.getApplicationInfo(disabledPackageName, PackageManager.GET_ACTIVITIES)
val toast = Toast.makeText(
context,
getString(
R.string.toast_hint_enable_sim_toolkit,
context.packageManager.getApplicationLabel(appInfo)
),
Toast.LENGTH_LONG,
)
startActivity(Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).apply {
data = Uri.fromParts("package", disabledPackageName, null)
})
toast.show()
return true
} else {
return false
} }
} }
} }

View file

@ -1,92 +1,106 @@
package im.angry.openeuicc.util package im.angry.openeuicc.util
import android.app.Activity
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 pm: PackageManager
get() = context.packageManager
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))
} }
private val packageNames = buildSet { operator fun get(slotId: Int): Slot? = when (slotId) {
addAll(slotSelection.map { it.packageName }) -1, EuiccChannelManager.USB_CHANNEL_ID -> null
addAll(slots.values.flatten().map { it.packageName }) else -> Slot(context, buildSet {
addAll(slots.getOrDefault(slotId, emptySet()))
addAll(slots.getOrDefault(-1, emptySet()))
})
} }
private val activities: Iterable<ComponentName> data class Slot(private val context: Context, private val components: Set<ComponentName>) {
get() = packageNames.flatMap(::getActivities).toSet() private val packageManager: PackageManager
get() = context.packageManager
private val launchIntent: Intent? private val packageNames: Iterable<String>
get() = packageNames.firstNotNullOfOrNull(::getLaunchIntent) get() = components.map(ComponentName::getPackageName).toSet()
private fun getLaunchIntent(packageName: String) = try { private val launchIntent: Intent?
pm.getLaunchIntentForPackage(packageName) get() = packageNames.firstNotNullOfOrNull(packageManager::getLaunchIntent)
} catch (_: PackageManager.NameNotFoundException) {
null
}
private fun getActivities(packageName: String) = buildList { private val activities: Iterable<ComponentName>
val activities = try { get() = packageNames.flatMap(packageManager::getActivities)
pm.getPackageInfo(packageName, PackageManager.GET_ACTIVITIES).activities ?: emptyArray() .filter(ActivityInfo::exported).map { ComponentName(it.packageName, it.name) }
} catch (_: PackageManager.NameNotFoundException) {
emptyArray()
}
for (activity in activities) {
if (!activity.exported) continue
add(ComponentName(packageName, activity.name))
}
}
private fun getComponentNames(@ArrayRes id: Int) = val available: Boolean
context.resources.getStringArray(id).mapNotNull(ComponentName::unflattenFromString) get() = getIntent() != null || getDisabledPackageName() != null
fun isAvailable(slotId: Int) = when (slotId) { private fun getIntent(): Intent? {
-1 -> false for (activity in activities) {
EuiccChannelManager.USB_CHANNEL_ID -> false if (!components.contains(activity)) continue
else -> intent(slotId) != null || getDisabledPackageName(slotId) != null if (isDisabledState(packageManager.getComponentEnabledSetting(activity))) continue
} return Intent.makeMainActivity(activity)
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)
}
return when (pm.getComponentEnabledSetting(intent.component ?: return launchIntent)) {
PackageManager.COMPONENT_ENABLED_STATE_ENABLED -> intent
else -> null
}
}
fun getDisabledPackageName(slotId: Int): String? {
val packageNames = buildSet {
addAll(slots.getOrDefault(slotId, emptySet()).map { it.packageName })
addAll(slotSelection.map { it.packageName })
}
return packageNames.find {
val enabledState = try {
pm.getApplicationEnabledSetting(it)
} catch (e: IllegalArgumentException) {
PackageManager.COMPONENT_ENABLED_STATE_DEFAULT
} }
when (enabledState) { return launchIntent
PackageManager.COMPONENT_ENABLED_STATE_DISABLED -> true }
PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER -> true
else -> false private fun getDisabledPackageName() = packageNames.find {
try {
isDisabledState(packageManager.getApplicationEnabledSetting(it))
} catch (_: IllegalArgumentException) {
false
} }
} }
fun launch(): Boolean {
var intent = getIntent()
if (intent == null) {
val pkgName = getDisabledPackageName() ?: return false
val message = context.getString(
R.string.toast_prompt_to_enable_sim_toolkit,
packageManager.getApplicationLabel(pkgName)
)
intent = Intent(
Settings.ACTION_APPLICATION_DETAILS_SETTINGS,
Uri.fromParts("package", pkgName, null)
)
Toast.makeText(context, message, Toast.LENGTH_LONG).show()
}
context.startActivity(intent)
return true
}
} }
} }
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.getApplicationLabel(packageName: String): CharSequence =
getApplicationLabel(getApplicationInfo(packageName, 0))
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,7 @@
<!-- 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_hint_enable_sim_toolkit">Please ENABLE your "%s" application</string> <string name="toast_prompt_to_enable_sim_toolkit">Please ENABLE your \&quot;%s\&quot; 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>