forked from PeterCxy/OpenEUICC
Compare commits
No commits in common. "quick-availability" and "master" have entirely different histories.
quick-avai
...
master
12 changed files with 6 additions and 311 deletions
|
@ -23,11 +23,6 @@
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
<activity
|
|
||||||
android:name="im.angry.openeuicc.ui.QuickAvailabilityActivity"
|
|
||||||
android:exported="false"
|
|
||||||
android:label="@string/quick_availability" />
|
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name="im.angry.openeuicc.ui.CompatibilityCheckActivity"
|
android:name="im.angry.openeuicc.ui.CompatibilityCheckActivity"
|
||||||
android:exported="false"
|
android:exported="false"
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
package im.angry.openeuicc.di
|
package im.angry.openeuicc.di
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import im.angry.openeuicc.util.UnprivilegedPreferenceRepository
|
|
||||||
|
|
||||||
class UnprivilegedAppContainer(context: Context) : DefaultAppContainer(context) {
|
class UnprivilegedAppContainer(context: Context) : DefaultAppContainer(context) {
|
||||||
override val uiComponentFactory by lazy {
|
override val uiComponentFactory by lazy {
|
||||||
|
@ -11,8 +10,4 @@ class UnprivilegedAppContainer(context: Context) : DefaultAppContainer(context)
|
||||||
override val customizableTextProvider by lazy {
|
override val customizableTextProvider by lazy {
|
||||||
UnprivilegedCustomizableTextProvider(context)
|
UnprivilegedCustomizableTextProvider(context)
|
||||||
}
|
}
|
||||||
|
|
||||||
override val preferenceRepository by lazy {
|
|
||||||
UnprivilegedPreferenceRepository(context)
|
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -2,12 +2,12 @@ package im.angry.openeuicc.di
|
||||||
|
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import im.angry.openeuicc.ui.EuiccManagementFragment
|
import im.angry.openeuicc.ui.EuiccManagementFragment
|
||||||
import im.angry.openeuicc.ui.QuickAvailabilityFragment
|
import im.angry.openeuicc.ui.SettingsFragment
|
||||||
import im.angry.openeuicc.ui.UnprivilegedEuiccManagementFragment
|
import im.angry.openeuicc.ui.UnprivilegedEuiccManagementFragment
|
||||||
import im.angry.openeuicc.ui.UnprivilegedNoEuiccPlaceholderFragment
|
import im.angry.openeuicc.ui.UnprivilegedNoEuiccPlaceholderFragment
|
||||||
import im.angry.openeuicc.ui.UnprivilegedSettingsFragment
|
import im.angry.openeuicc.ui.UnprivilegedSettingsFragment
|
||||||
|
|
||||||
open class UnprivilegedUiComponentFactory : DefaultUiComponentFactory() {
|
class UnprivilegedUiComponentFactory : DefaultUiComponentFactory() {
|
||||||
override fun createEuiccManagementFragment(slotId: Int, portId: Int): EuiccManagementFragment =
|
override fun createEuiccManagementFragment(slotId: Int, portId: Int): EuiccManagementFragment =
|
||||||
UnprivilegedEuiccManagementFragment.newInstance(slotId, portId)
|
UnprivilegedEuiccManagementFragment.newInstance(slotId, portId)
|
||||||
|
|
||||||
|
@ -16,7 +16,4 @@ open class UnprivilegedUiComponentFactory : DefaultUiComponentFactory() {
|
||||||
|
|
||||||
override fun createSettingsFragment(): Fragment =
|
override fun createSettingsFragment(): Fragment =
|
||||||
UnprivilegedSettingsFragment()
|
UnprivilegedSettingsFragment()
|
||||||
|
|
||||||
open fun createQuickAvailabilityFragment(): Fragment =
|
|
||||||
QuickAvailabilityFragment()
|
|
||||||
}
|
}
|
|
@ -1,37 +0,0 @@
|
||||||
package im.angry.openeuicc.ui
|
|
||||||
|
|
||||||
import android.content.Intent
|
|
||||||
import android.os.Bundle
|
|
||||||
import androidx.activity.enableEdgeToEdge
|
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
|
||||||
import im.angry.easyeuicc.R
|
|
||||||
import im.angry.openeuicc.di.UnprivilegedUiComponentFactory
|
|
||||||
import im.angry.openeuicc.util.OpenEuiccContextMarker
|
|
||||||
|
|
||||||
class QuickAvailabilityActivity : AppCompatActivity(), OpenEuiccContextMarker {
|
|
||||||
companion object {
|
|
||||||
const val EXTRA_FROM = "from_quick_availability"
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
|
||||||
super.onCreate(savedInstanceState)
|
|
||||||
enableEdgeToEdge()
|
|
||||||
setContentView(R.layout.activity_quick_availability)
|
|
||||||
|
|
||||||
val quickAvailabilityFragment =
|
|
||||||
(appContainer.uiComponentFactory as UnprivilegedUiComponentFactory)
|
|
||||||
.createQuickAvailabilityFragment()
|
|
||||||
|
|
||||||
supportFragmentManager.beginTransaction()
|
|
||||||
.replace(R.id.quick_availability_container, quickAvailabilityFragment)
|
|
||||||
.commit()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun launchMainActivity() {
|
|
||||||
val intent = packageManager.getLaunchIntentForPackage(packageName)!!
|
|
||||||
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
|
|
||||||
intent.putExtra(EXTRA_FROM, true)
|
|
||||||
startActivity(intent)
|
|
||||||
finish()
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,140 +0,0 @@
|
||||||
package im.angry.openeuicc.ui
|
|
||||||
|
|
||||||
import android.content.pm.PackageManager
|
|
||||||
import android.icu.text.ListFormatter
|
|
||||||
import android.os.Build
|
|
||||||
import android.os.Bundle
|
|
||||||
import android.view.LayoutInflater
|
|
||||||
import android.view.View
|
|
||||||
import android.view.ViewGroup
|
|
||||||
import android.widget.Button
|
|
||||||
import android.widget.CheckBox
|
|
||||||
import android.widget.TextView
|
|
||||||
import androidx.core.view.isVisible
|
|
||||||
import androidx.fragment.app.Fragment
|
|
||||||
import androidx.lifecycle.lifecycleScope
|
|
||||||
import im.angry.easyeuicc.R
|
|
||||||
import im.angry.openeuicc.util.EUICC_DEFAULT_ISDR_AID
|
|
||||||
import im.angry.openeuicc.util.UnprivilegedEuiccContextMarker
|
|
||||||
import im.angry.openeuicc.util.connectSEService
|
|
||||||
import im.angry.openeuicc.util.decodeHex
|
|
||||||
import im.angry.openeuicc.util.isSIM
|
|
||||||
import im.angry.openeuicc.util.slotIndex
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import kotlinx.coroutines.runBlocking
|
|
||||||
|
|
||||||
open class QuickAvailabilityFragment : Fragment(), UnprivilegedEuiccContextMarker {
|
|
||||||
companion object {
|
|
||||||
enum class Compatibility {
|
|
||||||
COMPATIBLE,
|
|
||||||
NOT_COMPATIBLE,
|
|
||||||
}
|
|
||||||
|
|
||||||
data class CompatibilityResult(
|
|
||||||
val compatibility: Compatibility,
|
|
||||||
val slots: List<String> = emptyList()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
private val conclusion: TextView by lazy {
|
|
||||||
requireView().requireViewById(R.id.quick_availability_conclusion)
|
|
||||||
}
|
|
||||||
|
|
||||||
private val resultSlots: TextView by lazy {
|
|
||||||
requireView().requireViewById(R.id.quick_availability_result_slots)
|
|
||||||
}
|
|
||||||
|
|
||||||
private val resultNotes: TextView by lazy {
|
|
||||||
requireView().requireViewById(R.id.quick_availability_result_notes)
|
|
||||||
}
|
|
||||||
|
|
||||||
private val hidden: CheckBox by lazy {
|
|
||||||
requireView().requireViewById(R.id.quick_availability_hidden)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCreateView(
|
|
||||||
inflater: LayoutInflater,
|
|
||||||
container: ViewGroup?,
|
|
||||||
savedInstanceState: Bundle?
|
|
||||||
): View = inflater.inflate(R.layout.fragment_quick_availability, container, false).apply {
|
|
||||||
requireViewById<TextView>(R.id.quick_availability_device_information)
|
|
||||||
.text = formatDeviceInformation()
|
|
||||||
requireViewById<Button>(R.id.quick_availability_button_continue)
|
|
||||||
.setOnClickListener { onContinueToApp() }
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onStart() {
|
|
||||||
super.onStart()
|
|
||||||
lifecycleScope.launch {
|
|
||||||
onCompatibilityUpdate(getCompatibilityCheckResult())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun onContinueToApp() {
|
|
||||||
runBlocking {
|
|
||||||
preferenceRepository.skipQuickAvailabilityFlow
|
|
||||||
.updatePreference(hidden.isChecked)
|
|
||||||
}
|
|
||||||
(requireActivity() as QuickAvailabilityActivity).launchMainActivity()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun onCompatibilityUpdate(result: CompatibilityResult) {
|
|
||||||
conclusion.text = formatConclusion(result)
|
|
||||||
if (result.compatibility != Compatibility.COMPATIBLE) return
|
|
||||||
resultSlots.isVisible = true
|
|
||||||
resultSlots.text = getString(
|
|
||||||
R.string.quick_availability_result_slots,
|
|
||||||
ListFormatter.getInstance().format(result.slots)
|
|
||||||
)
|
|
||||||
resultNotes.isVisible = true
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun getCompatibilityCheckResult(): CompatibilityResult {
|
|
||||||
val service = connectSEService(requireContext())
|
|
||||||
if (!service.isConnected) {
|
|
||||||
return CompatibilityResult(Compatibility.NOT_COMPATIBLE)
|
|
||||||
}
|
|
||||||
val slots = service.readers.filter { it.isSIM }.mapNotNull { reader ->
|
|
||||||
try {
|
|
||||||
// Note: we ONLY check the default ISD-R AID, because this test is for the _device_,
|
|
||||||
// NOT the eUICC. We don't care what AID a potential eUICC might use, all we need to
|
|
||||||
// check is we can open _some_ AID.
|
|
||||||
reader.openSession().openLogicalChannel(EUICC_DEFAULT_ISDR_AID.decodeHex())?.close()
|
|
||||||
reader.slotIndex
|
|
||||||
} catch (_: SecurityException) {
|
|
||||||
// Ignore; this is expected when everything works
|
|
||||||
// ref: https://android.googlesource.com/platform/frameworks/base/+/4fe64fb4712a99d5da9c9a0eb8fd5169b252e1e1/omapi/java/android/se/omapi/Session.java#305
|
|
||||||
// SecurityException is only thrown when Channel is constructed, which means everything else needs to succeed
|
|
||||||
reader.slotIndex
|
|
||||||
} catch (_: Exception) {
|
|
||||||
null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (slots.isEmpty()) {
|
|
||||||
return CompatibilityResult(Compatibility.NOT_COMPATIBLE)
|
|
||||||
}
|
|
||||||
return CompatibilityResult(Compatibility.COMPATIBLE, slots = slots.map { "SIM$it" })
|
|
||||||
}
|
|
||||||
|
|
||||||
open fun formatConclusion(result: CompatibilityResult): String {
|
|
||||||
val usbHost = requireContext().packageManager
|
|
||||||
.hasSystemFeature(PackageManager.FEATURE_USB_HOST)
|
|
||||||
val resId = when (result.compatibility) {
|
|
||||||
Compatibility.COMPATIBLE ->
|
|
||||||
R.string.quick_availability_compatible
|
|
||||||
|
|
||||||
Compatibility.NOT_COMPATIBLE -> if (usbHost)
|
|
||||||
R.string.quick_availability_not_compatible_but_usb else
|
|
||||||
R.string.quick_availability_not_compatible
|
|
||||||
}
|
|
||||||
return getString(resId, getString(R.string.app_name))
|
|
||||||
}
|
|
||||||
|
|
||||||
open fun formatDeviceInformation() = buildString {
|
|
||||||
appendLine("BRAND: ${Build.BRAND}")
|
|
||||||
appendLine("DEVICE: ${Build.DEVICE}")
|
|
||||||
appendLine("MODEL: ${Build.MODEL}")
|
|
||||||
appendLine("VERSION.RELEASE: ${Build.VERSION.RELEASE}")
|
|
||||||
appendLine("VERSION.SDK_INT: ${Build.VERSION.SDK_INT}")
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,27 +1,11 @@
|
||||||
package im.angry.openeuicc.ui
|
package im.angry.openeuicc.ui
|
||||||
|
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.Bundle
|
|
||||||
import android.view.Menu
|
import android.view.Menu
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
import im.angry.easyeuicc.R
|
import im.angry.easyeuicc.R
|
||||||
import im.angry.openeuicc.di.UnprivilegedUiComponentFactory
|
|
||||||
import im.angry.openeuicc.util.UnprivilegedEuiccContextMarker
|
|
||||||
import kotlinx.coroutines.flow.first
|
|
||||||
import kotlinx.coroutines.runBlocking
|
|
||||||
|
|
||||||
class UnprivilegedMainActivity : MainActivity(), UnprivilegedEuiccContextMarker {
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
|
||||||
super.onCreate(savedInstanceState)
|
|
||||||
if (intent.getBooleanExtra(QuickAvailabilityActivity.EXTRA_FROM, false)) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (runBlocking { !preferenceRepository.skipQuickAvailabilityFlow.first() }) {
|
|
||||||
startActivity(Intent(this, QuickAvailabilityActivity::class.java))
|
|
||||||
finish()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
class UnprivilegedMainActivity: MainActivity() {
|
||||||
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
||||||
super.onCreateOptionsMenu(menu)
|
super.onCreateOptionsMenu(menu)
|
||||||
menuInflater.inflate(R.menu.activity_main_unprivileged, menu)
|
menuInflater.inflate(R.menu.activity_main_unprivileged, menu)
|
||||||
|
|
|
@ -33,10 +33,10 @@ suspend fun List<CompatibilityCheck>.executeAll(callback: () -> Unit) = withCont
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val Reader.isSIM: Boolean
|
private val Reader.isSIM: Boolean
|
||||||
get() = name.startsWith("SIM")
|
get() = name.startsWith("SIM")
|
||||||
|
|
||||||
val Reader.slotIndex: Int
|
private val Reader.slotIndex: Int
|
||||||
get() = (name.replace("SIM", "").toIntOrNull() ?: 1)
|
get() = (name.replace("SIM", "").toIntOrNull() ?: 1)
|
||||||
|
|
||||||
abstract class CompatibilityCheck(context: Context) {
|
abstract class CompatibilityCheck(context: Context) {
|
||||||
|
@ -173,7 +173,7 @@ internal class IsdrChannelAccessCheck(private val context: Context): Compatibili
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (result != State.SUCCESS && validSlotIds.isNotEmpty()) {
|
if (result != State.SUCCESS && validSlotIds.size > 0) {
|
||||||
if (!context.packageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY_EUICC)) {
|
if (!context.packageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY_EUICC)) {
|
||||||
failureDescription = context.getString(
|
failureDescription = context.getString(
|
||||||
R.string.compatibility_check_isdr_channel_desc_partial_fail,
|
R.string.compatibility_check_isdr_channel_desc_partial_fail,
|
||||||
|
|
|
@ -1,14 +0,0 @@
|
||||||
package im.angry.openeuicc.util
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import androidx.datastore.preferences.core.booleanPreferencesKey
|
|
||||||
|
|
||||||
internal object UnprivilegedPreferenceKeys {
|
|
||||||
// ---- Miscellaneous ----
|
|
||||||
val SKIP_QUICK_AVAILABILITY = booleanPreferencesKey("skip_quick_availability")
|
|
||||||
}
|
|
||||||
|
|
||||||
class UnprivilegedPreferenceRepository(context: Context) : PreferenceRepository(context) {
|
|
||||||
// ---- Miscellaneous ----
|
|
||||||
val skipQuickAvailabilityFlow = bindFlow(UnprivilegedPreferenceKeys.SKIP_QUICK_AVAILABILITY, false)
|
|
||||||
}
|
|
|
@ -1,6 +0,0 @@
|
||||||
package im.angry.openeuicc.util
|
|
||||||
|
|
||||||
interface UnprivilegedEuiccContextMarker : OpenEuiccContextMarker {
|
|
||||||
override val preferenceRepository: UnprivilegedPreferenceRepository
|
|
||||||
get() = appContainer.preferenceRepository as UnprivilegedPreferenceRepository
|
|
||||||
}
|
|
|
@ -1,16 +0,0 @@
|
||||||
<?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="match_parent"
|
|
||||||
android:layout_height="match_parent">
|
|
||||||
|
|
||||||
<FrameLayout
|
|
||||||
android:id="@+id/quick_availability_container"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
|
||||||
app:layout_constraintLeft_toLeftOf="parent"
|
|
||||||
app:layout_constraintRight_toRightOf="parent"
|
|
||||||
app:layout_constraintTop_toBottomOf="parent" />
|
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
|
@ -1,53 +0,0 @@
|
||||||
<?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="match_parent"
|
|
||||||
android:gravity="center"
|
|
||||||
android:orientation="vertical"
|
|
||||||
android:padding="32dp"
|
|
||||||
android:textAlignment="center">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/quick_availability_conclusion"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginBottom="16dp"
|
|
||||||
android:textAlignment="center"
|
|
||||||
android:textSize="20sp"
|
|
||||||
android:textStyle="bold" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/quick_availability_device_information"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:fontFamily="monospace"
|
|
||||||
android:lineHeight="30dp"
|
|
||||||
android:textAlignment="center" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/quick_availability_result_slots"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginBottom="16dp"
|
|
||||||
android:visibility="gone" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/quick_availability_result_notes"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="@string/quick_availability_result_notes"
|
|
||||||
android:visibility="gone" />
|
|
||||||
|
|
||||||
<CheckBox
|
|
||||||
android:id="@+id/quick_availability_hidden"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="@string/quick_availability_hidden" />
|
|
||||||
|
|
||||||
<Button
|
|
||||||
android:id="@+id/quick_availability_button_continue"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="@string/quick_availability_button_continue" />
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
|
@ -11,16 +11,6 @@
|
||||||
<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>
|
<string name="toast_prompt_to_enable_sim_toolkit">Please ENABLE your \"%s\" application</string>
|
||||||
|
|
||||||
<!-- Quick Availability -->
|
|
||||||
<string name="quick_availability">Quick Availability</string>
|
|
||||||
<string name="quick_availability_compatible">Your smartphone can use %s compatible cards</string>
|
|
||||||
<string name="quick_availability_not_compatible">Your smartphone is not compatible with %s</string>
|
|
||||||
<string name="quick_availability_not_compatible_but_usb">Your smartphone is not compatible with %s, but can be managed using a USB smart card reader</string>
|
|
||||||
<string name="quick_availability_result_slots">SIM card slots accessible: %s</string>
|
|
||||||
<string name="quick_availability_result_notes">Note: Quick Compatibility Check results are for reference only. Actual usage may vary based on card insertion.</string>
|
|
||||||
<string name="quick_availability_hidden">Do not show this message again.</string>
|
|
||||||
<string name="quick_availability_button_continue">Continue</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>
|
||||||
<string name="compatibility_check_system_features_desc">Whether your device has all the required features for managing removable eUICC cards. For example, basic telephony and OMAPI support.</string>
|
<string name="compatibility_check_system_features_desc">Whether your device has all the required features for managing removable eUICC cards. For example, basic telephony and OMAPI support.</string>
|
||||||
|
|
Loading…
Add table
Reference in a new issue