feat: prompt to enable disabled sim toolkit app #153

Merged
PeterCxy merged 1 commit from septs/OpenEUICC:disabled-stk-app into master 2025-03-05 14:18:57 +01:00
3 changed files with 84 additions and 44 deletions

View file

@ -1,7 +1,10 @@
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
@ -24,8 +27,22 @@ 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
intent = stk.intent(slotId) isVisible = slot.intent != null
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,65 +3,87 @@ 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))
} }
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.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()
private val launchIntent by lazy { private val launchIntent: Intent?
packageNames.firstNotNullOfOrNull(::getLaunchIntent) get() = packageNames.firstNotNullOfOrNull(packageManager::getLaunchIntent)
private val activities: Iterable<ComponentName>
get() = packageNames.flatMap(packageManager::getActivities)
.filter(ActivityInfo::exported).map { ComponentName(it.packageName, it.name) }
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 getLaunchIntent(packageName: String) = try { private fun getDisabledPackageIntent(): Intent? {
val pm = context.packageManager val disabledPackageName = packageNames.find {
pm.getLaunchIntentForPackage(packageName) try {
isDisabledState(packageManager.getApplicationEnabledSetting(it))
} catch (_: IllegalArgumentException) {
false
}
}
if (disabledPackageName == null) return null
return Intent(
Settings.ACTION_APPLICATION_DETAILS_SETTINGS,
Uri.fromParts("package", disabledPackageName, null)
)
}
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) { } catch (_: PackageManager.NameNotFoundException) {
null null
} }
private fun getActivities(packageName: String): List<ComponentName> { private fun PackageManager.getActivities(packageName: String) = try {
return try { getPackageInfo(packageName, PackageManager.GET_ACTIVITIES)
val pm = context.packageManager .activities?.toList() ?: emptyList()
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) { } catch (_: PackageManager.NameNotFoundException) {
emptyList() emptyList()
} }
}
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)
}
return if (intent.component != null) intent else launchIntent
}
}

View file

@ -9,6 +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_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>