Compare commits

..

No commits in common. "9d18253e44574a4cf518523a339e4cada2f3f57c" and "aed247904487ba9b2f01ac2030406a9bd0c1c37c" have entirely different histories.

32 changed files with 183 additions and 263 deletions

View file

@ -37,7 +37,6 @@ open class DefaultEuiccChannelFactory(protected val context: Context) : EuiccCha
return EuiccChannelImpl(
context.getString(R.string.omapi),
port,
intrinsicChannelName = null,
OmapiApduInterface(
seService!!,
port,
@ -68,7 +67,6 @@ open class DefaultEuiccChannelFactory(protected val context: Context) : EuiccCha
return EuiccChannelImpl(
context.getString(R.string.usb),
FakeUiccPortInfoCompat(FakeUiccCardInfoCompat(EuiccChannelManager.USB_CHANNEL_ID)),
intrinsicChannelName = usbDevice.productName,
UsbApduInterface(
conn,
bulkIn,

View file

@ -16,12 +16,5 @@ interface EuiccChannel {
val valid: Boolean
/**
* Intrinsic name of this channel. For device-internal SIM slots,
* this should be null; for USB readers, this should be the name of
* the reader device.
*/
val intrinsicChannelName: String?
fun close()
}

View file

@ -10,7 +10,6 @@ import net.typeblog.lpac_jni.impl.LocalProfileAssistantImpl
class EuiccChannelImpl(
override val type: String,
override val port: UiccPortInfoCompat,
override val intrinsicChannelName: String?,
apduInterface: ApduInterface,
verboseLoggingFlow: Flow<Boolean>,
ignoreTLSCertificateFlow: Flow<Boolean>

View file

@ -31,8 +31,6 @@ class EuiccChannelWrapper(orig: EuiccChannel) : EuiccChannel {
override val lpa: LocalProfileAssistant by lpaDelegate
override val valid: Boolean
get() = channel.valid
override val intrinsicChannelName: String?
get() = channel.intrinsicChannelName
override fun close() = channel.close()

View file

@ -15,5 +15,4 @@ interface AppContainer {
val preferenceRepository: PreferenceRepository
val uiComponentFactory: UiComponentFactory
val euiccChannelFactory: EuiccChannelFactory
val customizableTextProvider: CustomizableTextProvider
}

View file

@ -1,20 +0,0 @@
package im.angry.openeuicc.di
interface CustomizableTextProvider {
/**
* Explanation string for when no eUICC is found on the device.
* This could be different depending on whether the app is privileged or not.
*/
val noEuiccExplanation: String
/**
* Shown when we timed out switching between profiles.
*/
val profileSwitchingTimeoutMessage: String
/**
* Format the name of a logical slot; internal only -- not intended for
* other channels such as USB.
*/
fun formatInternalChannelName(logicalSlotId: Int): String
}

View file

@ -38,8 +38,4 @@ open class DefaultAppContainer(context: Context) : AppContainer {
override val euiccChannelFactory by lazy {
DefaultEuiccChannelFactory(context)
}
override val customizableTextProvider by lazy {
DefaultCustomizableTextProvider(context)
}
}

View file

@ -1,15 +0,0 @@
package im.angry.openeuicc.di
import android.content.Context
import im.angry.openeuicc.common.R
open class DefaultCustomizableTextProvider(private val context: Context) : CustomizableTextProvider {
override val noEuiccExplanation: String
get() = context.getString(R.string.no_euicc)
override val profileSwitchingTimeoutMessage: String
get() = context.getString(R.string.enable_disable_timeout)
override fun formatInternalChannelName(logicalSlotId: Int): String =
context.getString(R.string.channel_name_format, logicalSlotId)
}

View file

@ -1,18 +1,13 @@
package im.angry.openeuicc.ui
import android.annotation.SuppressLint
import android.content.ClipData
import android.content.ClipboardManager
import android.os.Build
import android.os.Bundle
import android.view.LayoutInflater
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import android.widget.Toast
import androidx.activity.enableEdgeToEdge
import androidx.annotation.StringRes
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.DividerItemDecoration
import androidx.recyclerview.widget.LinearLayoutManager
@ -37,13 +32,6 @@ class EuiccInfoActivity : BaseEuiccAccessActivity() {
private var logicalSlotId: Int = -1
data class Item(
@StringRes
val titleResId: Int,
val content: String?,
val copiedToastResId: Int? = null
)
override fun onCreate(savedInstanceState: Bundle?) {
enableEdgeToEdge()
super.onCreate(savedInstanceState)
@ -53,11 +41,12 @@ class EuiccInfoActivity : BaseEuiccAccessActivity() {
supportActionBar!!.setDisplayHomeAsUpEnabled(true)
swipeRefresh = requireViewById(R.id.swipe_refresh)
infoList = requireViewById<RecyclerView>(R.id.recycler_view).also {
it.layoutManager = LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false)
it.addItemDecoration(DividerItemDecoration(this, LinearLayoutManager.VERTICAL))
it.adapter = EuiccInfoAdapter()
}
infoList = requireViewById(R.id.recycler_view)
infoList.layoutManager =
LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false)
infoList.addItemDecoration(DividerItemDecoration(this, LinearLayoutManager.VERTICAL))
infoList.adapter = EuiccInfoAdapter()
logicalSlotId = intent.getIntExtra("logicalSlotId", 0)
@ -92,33 +81,29 @@ class EuiccInfoActivity : BaseEuiccAccessActivity() {
lifecycleScope.launch {
(infoList.adapter!! as EuiccInfoAdapter).euiccInfoItems =
euiccChannelManager.withEuiccChannel(logicalSlotId, ::buildEuiccInfoItems)
euiccChannelManager.withEuiccChannel(logicalSlotId, ::buildPairs).map {
Pair(getString(it.first), it.second ?: getString(R.string.unknown))
}
swipeRefresh.isRefreshing = false
}
}
private fun buildEuiccInfoItems(channel: EuiccChannel) = buildList {
add(Item(R.string.euicc_info_access_mode, channel.type))
private fun buildPairs(channel: EuiccChannel) = buildList {
add(Pair(R.string.euicc_info_access_mode, channel.type))
add(
Item(
Pair(
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
)
)
add(Pair(R.string.euicc_info_eid, channel.lpa.eID))
channel.lpa.euiccInfo2.let { info ->
add(Item(R.string.euicc_info_firmware_version, info?.euiccFirmwareVersion))
add(Item(R.string.euicc_info_globalplatform_version, info?.globalPlatformVersion))
add(Item(R.string.euicc_info_pp_version, info?.ppVersion))
add(Item(R.string.euicc_info_sas_accreditation_number, info?.sasAccreditationNumber))
add(Item(R.string.euicc_info_free_nvram, info?.freeNvram?.let(::formatFreeSpace)))
add(Pair(R.string.euicc_info_firmware_version, info?.euiccFirmwareVersion))
add(Pair(R.string.euicc_info_globalplatform_version, info?.globalPlatformVersion))
add(Pair(R.string.euicc_info_pp_version, info?.ppVersion))
add(Pair(R.string.euicc_info_sas_accreditation_number, info?.sasAccreditationNumber))
add(Pair(R.string.euicc_info_free_nvram, info?.freeNvram?.let(::formatFreeSpace)))
}
channel.lpa.euiccInfo2?.euiccCiPKIdListForSigning.orEmpty().let { signers ->
// SGP.28 v1.0, eSIM CI Registration Criteria (Page 5 of 9, 2019-10-24)
@ -131,7 +116,7 @@ class EuiccInfoActivity : BaseEuiccAccessActivity() {
PKID_GSMA_TEST_CI.any(signers::contains) -> R.string.euicc_info_ci_gsma_test
else -> R.string.euicc_info_ci_unknown
}
add(Item(R.string.euicc_info_ci_type, getString(resId)))
add(Pair(R.string.euicc_info_ci_type, getString(resId)))
}
}
@ -147,34 +132,15 @@ class EuiccInfoActivity : BaseEuiccAccessActivity() {
inner class EuiccInfoViewHolder(root: View) : ViewHolder(root) {
private val title: TextView = root.requireViewById(R.id.euicc_info_title)
private val content: TextView = root.requireViewById(R.id.euicc_info_content)
private var copiedToastResId: Int? = null
init {
root.setOnClickListener {
if (copiedToastResId != null) {
val label = title.text.toString()
getSystemService(ClipboardManager::class.java)!!
.setPrimaryClip(ClipData.newPlainText(label, content.text))
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.S_V2) {
Toast.makeText(
this@EuiccInfoActivity,
copiedToastResId!!,
Toast.LENGTH_SHORT
).show()
}
}
}
}
fun bind(item: Item) {
copiedToastResId = item.copiedToastResId
title.setText(item.titleResId)
content.text = item.content ?: getString(R.string.unknown)
fun bind(item: Pair<String, String>) {
title.text = item.first
content.text = item.second
}
}
inner class EuiccInfoAdapter : RecyclerView.Adapter<EuiccInfoViewHolder>() {
var euiccInfoItems: List<Item> = listOf()
var euiccInfoItems: List<Pair<String, String>> = listOf()
@SuppressLint("NotifyDataSetChanged")
set(newVal) {
field = newVal

View file

@ -4,7 +4,6 @@ import android.annotation.SuppressLint
import android.content.ClipData
import android.content.ClipboardManager
import android.content.Intent
import android.os.Build
import android.os.Bundle
import android.text.method.PasswordTransformationMethod
import android.view.LayoutInflater
@ -262,7 +261,7 @@ open class EuiccManagementFragment : Fragment(), EuiccProfilesChangedListener,
invalid = true
// Timed out waiting for SIM to come back online, we can no longer assume that the LPA is still valid
AlertDialog.Builder(requireContext()).apply {
setMessage(appContainer.customizableTextProvider.profileSwitchingTimeoutMessage)
setMessage(R.string.enable_disable_timeout)
setPositiveButton(android.R.string.ok) { dialog, _ ->
dialog.dismiss()
requireActivity().finish()
@ -349,8 +348,7 @@ open class EuiccManagementFragment : Fragment(), EuiccProfilesChangedListener,
iccid.setOnLongClickListener {
requireContext().getSystemService(ClipboardManager::class.java)!!
.setPrimaryClip(ClipData.newPlainText("iccid", iccid.text))
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.S_V2) Toast
.makeText(requireContext(), R.string.toast_iccid_copied, Toast.LENGTH_SHORT)
Toast.makeText(requireContext(), R.string.toast_iccid_copied, Toast.LENGTH_SHORT)
.show()
true
}

View file

@ -8,6 +8,7 @@ import android.view.View
import android.widget.ScrollView
import android.widget.TextView
import androidx.activity.enableEdgeToEdge
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.lifecycleScope
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
@ -16,6 +17,7 @@ import im.angry.openeuicc.util.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.io.FileOutputStream
import java.util.Date
class LogsActivity : AppCompatActivity() {
@ -25,15 +27,15 @@ class LogsActivity : AppCompatActivity() {
private lateinit var logStr: String
private val saveLogs =
setupLogSaving(
getLogFileName = {
getString(
R.string.logs_filename_template,
SimpleDateFormat.getDateTimeInstance().format(Date())
)
},
getLogText = { logStr }
)
registerForActivityResult(ActivityResultContracts.CreateDocument("text/plain")) { uri ->
if (uri == null) return@registerForActivityResult
if (!this::logStr.isInitialized) return@registerForActivityResult
contentResolver.openFileDescriptor(uri, "w")?.use {
FileOutputStream(it.fileDescriptor).use { os ->
os.write(logStr.encodeToByteArray())
}
}
}
override fun onCreate(savedInstanceState: Bundle?) {
enableEdgeToEdge()
@ -74,7 +76,9 @@ class LogsActivity : AppCompatActivity() {
true
}
R.id.save -> {
saveLogs()
saveLogs.launch(getString(R.string.logs_filename_template,
SimpleDateFormat.getDateTimeInstance().format(Date())
))
true
}
else -> super.onOptionsItemSelected(item)

View file

@ -163,8 +163,7 @@ open class MainActivity : BaseEuiccAccessActivity(), OpenEuiccContextMarker {
// but it could change in the future
euiccChannelManager.notifyEuiccProfilesChanged(channel.logicalSlotId)
val channelName =
appContainer.customizableTextProvider.formatInternalChannelName(channel.logicalSlotId)
val channelName = getString(R.string.channel_name_format, channel.logicalSlotId)
newPages.add(Page(channel.logicalSlotId, channelName) {
appContainer.uiComponentFactory.createEuiccManagementFragment(slotId, portId)
})

View file

@ -4,20 +4,15 @@ import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.fragment.app.Fragment
import im.angry.openeuicc.common.R
import im.angry.openeuicc.util.*
class NoEuiccPlaceholderFragment : Fragment(), OpenEuiccContextMarker {
class NoEuiccPlaceholderFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val view = inflater.inflate(R.layout.fragment_no_euicc_placeholder, container, false)
val textView = view.requireViewById<TextView>(R.id.no_euicc_placeholder)
textView.text = appContainer.customizableTextProvider.noEuiccExplanation
return view
return inflater.inflate(R.layout.fragment_no_euicc_placeholder, container, false)
}
}

View file

@ -6,6 +6,7 @@ import android.os.Build
import android.os.Bundle
import android.provider.Settings
import android.widget.Toast
import androidx.datastore.preferences.core.Preferences
import androidx.lifecycle.lifecycleScope
import androidx.preference.CheckBoxPreference
import androidx.preference.Preference
@ -13,6 +14,7 @@ import androidx.preference.PreferenceCategory
import androidx.preference.PreferenceFragmentCompat
import im.angry.openeuicc.common.R
import im.angry.openeuicc.util.*
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
@ -58,25 +60,25 @@ open class SettingsFragment: PreferenceFragmentCompat() {
}
findPreference<CheckBoxPreference>("pref_notifications_download")
?.bindBooleanFlow(preferenceRepository.notificationDownloadFlow)
?.bindBooleanFlow(preferenceRepository.notificationDownloadFlow, PreferenceKeys.NOTIFICATION_DOWNLOAD)
findPreference<CheckBoxPreference>("pref_notifications_delete")
?.bindBooleanFlow(preferenceRepository.notificationDeleteFlow)
?.bindBooleanFlow(preferenceRepository.notificationDeleteFlow, PreferenceKeys.NOTIFICATION_DELETE)
findPreference<CheckBoxPreference>("pref_notifications_switch")
?.bindBooleanFlow(preferenceRepository.notificationSwitchFlow)
?.bindBooleanFlow(preferenceRepository.notificationSwitchFlow, PreferenceKeys.NOTIFICATION_SWITCH)
findPreference<CheckBoxPreference>("pref_advanced_disable_safeguard_removable_esim")
?.bindBooleanFlow(preferenceRepository.disableSafeguardFlow)
?.bindBooleanFlow(preferenceRepository.disableSafeguardFlow, PreferenceKeys.DISABLE_SAFEGUARD_REMOVABLE_ESIM)
findPreference<CheckBoxPreference>("pref_advanced_verbose_logging")
?.bindBooleanFlow(preferenceRepository.verboseLoggingFlow)
?.bindBooleanFlow(preferenceRepository.verboseLoggingFlow, PreferenceKeys.VERBOSE_LOGGING)
findPreference<CheckBoxPreference>("pref_developer_unfiltered_profile_list")
?.bindBooleanFlow(preferenceRepository.unfilteredProfileListFlow)
?.bindBooleanFlow(preferenceRepository.unfilteredProfileListFlow, PreferenceKeys.UNFILTERED_PROFILE_LIST)
findPreference<CheckBoxPreference>("pref_ignore_tls_certificate")
?.bindBooleanFlow(preferenceRepository.ignoreTLSCertificateFlow)
?.bindBooleanFlow(preferenceRepository.ignoreTLSCertificateFlow, PreferenceKeys.IGNORE_TLS_CERTIFICATE)
}
override fun onStart() {
@ -97,7 +99,10 @@ open class SettingsFragment: PreferenceFragmentCompat() {
if (numClicks == 7) {
lifecycleScope.launch {
preferenceRepository.developerOptionsEnabledFlow.updatePreference(true)
preferenceRepository.updatePreference(
PreferenceKeys.DEVELOPER_OPTIONS_ENABLED,
true
)
lastToast?.cancel()
Toast.makeText(
@ -119,14 +124,14 @@ open class SettingsFragment: PreferenceFragmentCompat() {
return true
}
private fun CheckBoxPreference.bindBooleanFlow(flow: PreferenceFlowWrapper<Boolean>) {
private fun CheckBoxPreference.bindBooleanFlow(flow: Flow<Boolean>, key: Preferences.Key<Boolean>) {
lifecycleScope.launch {
flow.collect { isChecked = it }
}
setOnPreferenceChangeListener { _, newValue ->
runBlocking {
flow.updatePreference(newValue as Boolean)
preferenceRepository.updatePreference(key, newValue as Boolean)
}
true
}

View file

@ -0,0 +1,93 @@
package im.angry.openeuicc.ui
import android.content.DialogInterface
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ArrayAdapter
import android.widget.Spinner
import androidx.appcompat.widget.Toolbar
import im.angry.openeuicc.common.R
import im.angry.openeuicc.core.EuiccChannel
import im.angry.openeuicc.util.*
class SlotSelectFragment : BaseMaterialDialogFragment(), OpenEuiccContextMarker {
companion object {
const val TAG = "SlotSelectFragment"
fun newInstance(slotIds: List<Int>, logicalSlotIds: List<Int>, portIds: List<Int>): SlotSelectFragment {
return SlotSelectFragment().apply {
arguments = Bundle().apply {
putIntArray("slotIds", slotIds.toIntArray())
putIntArray("logicalSlotIds", logicalSlotIds.toIntArray())
putIntArray("portIds", portIds.toIntArray())
}
}
}
}
interface SlotSelectedListener {
fun onSlotSelected(slotId: Int, portId: Int)
fun onSlotSelectCancelled()
}
private lateinit var toolbar: Toolbar
private lateinit var spinner: Spinner
private lateinit var adapter: ArrayAdapter<String>
private lateinit var slotIds: IntArray
private lateinit var logicalSlotIds: IntArray
private lateinit var portIds: IntArray
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val view = inflater.inflate(R.layout.fragment_slot_select, container, false)
toolbar = view.requireViewById(R.id.toolbar)
toolbar.setTitle(R.string.slot_select)
toolbar.inflateMenu(R.menu.fragment_slot_select)
adapter = ArrayAdapter<String>(inflater.context, R.layout.spinner_item)
spinner = view.requireViewById(R.id.spinner)
spinner.adapter = adapter
return view
}
override fun onStart() {
super.onStart()
slotIds = requireArguments().getIntArray("slotIds")!!
logicalSlotIds = requireArguments().getIntArray("logicalSlotIds")!!
portIds = requireArguments().getIntArray("portIds")!!
logicalSlotIds.forEach { id ->
adapter.add(getString(R.string.channel_name_format, id))
}
toolbar.setNavigationOnClickListener {
(requireActivity() as SlotSelectedListener).onSlotSelectCancelled()
}
toolbar.setOnMenuItemClickListener {
val slotId = slotIds[spinner.selectedItemPosition]
val portId = portIds[spinner.selectedItemPosition]
(requireActivity() as SlotSelectedListener).onSlotSelected(slotId, portId)
dismiss()
true
}
}
override fun onResume() {
super.onResume()
setWidthPercent(75)
}
override fun onCancel(dialog: DialogInterface) {
super.onCancel(dialog)
(requireActivity() as SlotSelectedListener).onSlotSelectCancelled()
}
}

View file

@ -6,8 +6,10 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.activity.result.contract.ActivityResultContracts
import im.angry.openeuicc.common.R
import im.angry.openeuicc.util.*
import java.io.FileOutputStream
import java.util.Date
class DownloadWizardDiagnosticsFragment : DownloadWizardActivity.DownloadWizardStepFragment() {
@ -19,15 +21,14 @@ class DownloadWizardDiagnosticsFragment : DownloadWizardActivity.DownloadWizardS
private lateinit var diagnosticTextView: TextView
private val saveDiagnostics =
setupLogSaving(
getLogFileName = {
getString(
R.string.download_wizard_diagnostics_file_template,
SimpleDateFormat.getDateTimeInstance().format(Date())
)
},
getLogText = { diagnosticTextView.text.toString() }
)
registerForActivityResult(ActivityResultContracts.CreateDocument("text/plain")) { uri ->
if (uri == null) return@registerForActivityResult
requireActivity().contentResolver.openFileDescriptor(uri, "w")?.use {
FileOutputStream(it.fileDescriptor).use { os ->
os.write(diagnosticTextView.text.toString().encodeToByteArray())
}
}
}
override fun createNextFragment(): DownloadWizardActivity.DownloadWizardStepFragment? = null
@ -40,7 +41,12 @@ class DownloadWizardDiagnosticsFragment : DownloadWizardActivity.DownloadWizardS
): View? {
val view = inflater.inflate(R.layout.fragment_download_diagnostics, container, false)
view.requireViewById<View>(R.id.download_wizard_diagnostics_save).setOnClickListener {
saveDiagnostics()
saveDiagnostics.launch(
getString(
R.string.download_wizard_diagnostics_file_template,
SimpleDateFormat.getDateTimeInstance().format(Date())
)
)
}
diagnosticTextView = view.requireViewById(R.id.download_wizard_diagnostics_text)
return view

View file

@ -35,8 +35,7 @@ class DownloadWizardSlotSelectFragment : DownloadWizardActivity.DownloadWizardSt
val eID: String,
val freeSpace: Int,
val imei: String,
val enabledProfileName: String?,
val intrinsicChannelName: String?,
val enabledProfileName: String?
)
private var loaded = false
@ -62,9 +61,7 @@ class DownloadWizardSlotSelectFragment : DownloadWizardActivity.DownloadWizardSt
setMessage(R.string.profile_download_low_nvram_message)
setCancelable(true)
setPositiveButton(android.R.string.ok, null)
setNegativeButton(android.R.string.cancel) { _, _ ->
requireActivity().finish()
}
setNegativeButton(android.R.string.cancel, null)
show()
}
}
@ -109,8 +106,7 @@ class DownloadWizardSlotSelectFragment : DownloadWizardActivity.DownloadWizardSt
} catch (e: Exception) {
""
},
channel.lpa.profiles.find { it.state == LocalProfileInfo.State.Enabled }?.displayName,
channel.intrinsicChannelName,
channel.lpa.profiles.find { it.state == LocalProfileInfo.State.Enabled }?.displayName
)
}
}.toList().sortedBy { it.logicalSlotId }
@ -181,9 +177,9 @@ class DownloadWizardSlotSelectFragment : DownloadWizardActivity.DownloadWizardSt
}
title.text = if (item.logicalSlotId == EuiccChannelManager.USB_CHANNEL_ID) {
item.intrinsicChannelName ?: root.context.getString(R.string.usb)
root.context.getString(R.string.usb)
} else {
appContainer.customizableTextProvider.formatInternalChannelName(item.logicalSlotId)
root.context.getString(R.string.download_wizard_slot_title, item.logicalSlotId)
}
eID.text = item.eID
activeProfile.text = item.enabledProfileName ?: root.context.getString(R.string.unknown)

View file

@ -19,7 +19,7 @@ val Context.preferenceRepository: PreferenceRepository
val Fragment.preferenceRepository: PreferenceRepository
get() = requireContext().preferenceRepository
internal object PreferenceKeys {
object PreferenceKeys {
// ---- Profile Notifications ----
val NOTIFICATION_DOWNLOAD = booleanPreferencesKey("notification_download")
val NOTIFICATION_DELETE = booleanPreferencesKey("notification_delete")
@ -51,22 +51,9 @@ class PreferenceRepository(private val context: Context) {
val unfilteredProfileListFlow = bindFlow(PreferenceKeys.UNFILTERED_PROFILE_LIST, false)
val ignoreTLSCertificateFlow = bindFlow(PreferenceKeys.IGNORE_TLS_CERTIFICATE, false)
private fun <T> bindFlow(key: Preferences.Key<T>, defaultValue: T): PreferenceFlowWrapper<T> =
PreferenceFlowWrapper(context, key, defaultValue)
}
class PreferenceFlowWrapper<T> private constructor(
private val context: Context,
private val key: Preferences.Key<T>,
inner: Flow<T>
) : Flow<T> by inner {
internal constructor(context: Context, key: Preferences.Key<T>, defaultValue: T) : this(
context,
key,
private fun <T> bindFlow(key: Preferences.Key<T>, defaultValue: T): Flow<T> =
context.dataStore.data.map { it[key] ?: defaultValue }
)
suspend fun updatePreference(value: T) {
suspend fun <T> updatePreference(key: Preferences.Key<T>, value: T) =
context.dataStore.edit { it[key] = value }
}
}

View file

@ -1,23 +1,17 @@
package im.angry.openeuicc.util
import android.content.Context
import android.content.Intent
import android.content.res.Resources
import android.graphics.Rect
import android.view.View
import android.view.ViewGroup
import androidx.activity.result.ActivityResultCaller
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.widget.Toolbar
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.updateLayoutParams
import androidx.core.view.updatePadding
import androidx.fragment.app.DialogFragment
import androidx.fragment.app.Fragment
import im.angry.openeuicc.common.R
import java.io.FileOutputStream
// Source: <https://stackoverflow.com/questions/12478520/how-to-set-dialogfragments-width-and-height>
/**
@ -75,44 +69,4 @@ fun setupRootViewInsets(view: ViewGroup) {
WindowInsetsCompat.CONSUMED
}
}
fun <T : ActivityResultCaller> T.setupLogSaving(
getLogFileName: () -> String,
getLogText: () -> String
): () -> Unit {
val launchSaveIntent =
registerForActivityResult(ActivityResultContracts.CreateDocument("text/plain")) { uri ->
if (uri == null) return@registerForActivityResult
val context = when (this@setupLogSaving) {
is Context -> this@setupLogSaving
is Fragment -> requireContext()
else -> throw IllegalArgumentException("Must be either Context or Fragment!")
}
context.contentResolver.openFileDescriptor(uri, "w")?.use {
FileOutputStream(it.fileDescriptor).use { os ->
os.write(getLogText().encodeToByteArray())
}
}
AlertDialog.Builder(context).apply {
setMessage(R.string.logs_saved_message)
setNegativeButton(R.string.no) { _, _ -> }
setPositiveButton(R.string.yes) { _, _ ->
val intent = Intent().apply {
action = Intent.ACTION_SEND
type = "text/plain"
putExtra(Intent.EXTRA_STREAM, uri)
}
context.startActivity(Intent.createChooser(intent, null))
}
}.show()
}
return {
launchSaveIntent.launch(getLogFileName())
}
}

View file

@ -43,6 +43,7 @@
<string name="download_wizard_back">戻る</string>
<string name="download_wizard_next">次へ</string>
<string name="download_wizard_slot_select">ダウンロードする eSIM を選択または確認:</string>
<string name="download_wizard_slot_title">論理スロット %d</string>
<string name="download_wizard_slot_type">タイプ:</string>
<string name="download_wizard_slot_type_removable">リムーバブル</string>
<string name="download_wizard_slot_type_internal">内部</string>

View file

@ -81,6 +81,7 @@
<string name="download_wizard_back">返回</string>
<string name="download_wizard_next">下一步</string>
<string name="download_wizard_slot_select">请选择或确认下载目标 eSIM 卡槽:</string>
<string name="download_wizard_slot_title">逻辑卡槽 %d</string>
<string name="download_wizard_slot_type">类型:</string>
<string name="download_wizard_slot_type_removable">可插拔</string>
<string name="download_wizard_slot_type_internal">内置</string>

View file

@ -31,7 +31,6 @@
<string name="toast_profile_name_too_long">Nickname cannot be longer than 64 characters</string>
<string name="toast_profile_delete_confirm_text_mismatched">Confirmation string mismatch</string>
<string name="toast_iccid_copied">ICCID copied to clipboard</string>
<string name="toast_eid_copied">EID copied to clipboard</string>
<string name="slot_select">Select Slot</string>
<string name="slot_select_select">Select</string>
@ -65,6 +64,7 @@
<string name="download_wizard_back">Back</string>
<string name="download_wizard_next">Next</string>
<string name="download_wizard_slot_select">Select or confirm the eSIM you would like to download to:</string>
<string name="download_wizard_slot_title">Logical slot %d</string>
<string name="download_wizard_slot_type">Type:</string>
<string name="download_wizard_slot_type_removable">Removable</string>
<string name="download_wizard_slot_type_internal">Internal</string>
@ -95,8 +95,6 @@
<string name="download_wizard_diagnostics_save">Save</string>
<string name="download_wizard_diagnostics_file_template">Diagnostics at %s</string>
<string name="logs_saved_message">Logs have been saved to the selected path. Would you like to share the log through another app?</string>
<string name="profile_rename_new_name">New nickname</string>
<string name="profile_delete_confirm">Are you sure you want to delete the profile %s? This operation is irreversible.</string>

View file

@ -6,8 +6,4 @@ class UnprivilegedAppContainer(context: Context) : DefaultAppContainer(context)
override val uiComponentFactory by lazy {
UnprivilegedUiComponentFactory()
}
override val customizableTextProvider by lazy {
UnprivilegedCustomizableTextProvider(context)
}
}

View file

@ -1,10 +0,0 @@
package im.angry.openeuicc.di
import android.content.Context
import im.angry.easyeuicc.R
class UnprivilegedCustomizableTextProvider(private val context: Context) :
DefaultCustomizableTextProvider(context) {
override fun formatInternalChannelName(logicalSlotId: Int): String =
context.getString(R.string.channel_name_format_unpriv, logicalSlotId)
}

View file

@ -3,7 +3,6 @@ package im.angry.openeuicc.ui
import android.content.ClipData
import android.content.ClipboardManager
import android.content.pm.PackageManager
import android.os.Build
import android.os.Bundle
import android.widget.Toast
import androidx.preference.Preference
@ -36,8 +35,7 @@ class UnprivilegedSettingsFragment : SettingsFragment() {
setOnPreferenceClickListener {
requireContext().getSystemService(ClipboardManager::class.java)!!
.setPrimaryClip(ClipData.newPlainText("ara-m", summary))
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.S_V2) Toast
.makeText(requireContext(), R.string.toast_ara_m_copied, Toast.LENGTH_SHORT)
Toast.makeText(requireContext(), R.string.toast_ara_m_copied, Toast.LENGTH_SHORT)
.show()
true
}

View file

@ -1,6 +1,6 @@
<resources>
<string name="app_name" translatable="false">EasyEUICC</string>
<string name="channel_name_format_unpriv" translatable="false">SIM %d</string>
<string name="channel_name_format" translatable="false">SIM %d</string>
<string name="compatibility_check">Compatibility Check</string>
<string name="open_sim_toolkit">Open SIM Toolkit</string>

View file

@ -30,7 +30,6 @@ class PrivilegedEuiccChannelFactory(context: Context) : DefaultEuiccChannelFacto
return EuiccChannelImpl(
context.getString(R.string.telephony_manager),
port,
intrinsicChannelName = null,
TelephonyManagerApduInterface(
port,
tm,

View file

@ -23,8 +23,4 @@ class PrivilegedAppContainer(context: Context) : DefaultAppContainer(context) {
override val euiccChannelFactory by lazy {
PrivilegedEuiccChannelFactory(context)
}
override val customizableTextProvider by lazy {
PrivilegedCustomizableTextProvider(context)
}
}

View file

@ -1,10 +0,0 @@
package im.angry.openeuicc.di
import android.content.Context
import im.angry.openeuicc.R
class PrivilegedCustomizableTextProvider(private val context: Context) :
DefaultCustomizableTextProvider(context) {
override val noEuiccExplanation: String
get() = context.getString(R.string.no_euicc_priv)
}

View file

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="no_euicc_priv">このデバイスで eUICC が見つかりません。\nデバイスによってはアプリのメニューからデュアル SIM を有効化する必要があります。</string>
<string name="no_euicc">このデバイスで eUICC が見つかりません。\nデバイスによってはアプリのメニューからデュアル SIM を有効化する必要があります。</string>
<string name="telephony_manager">TelephonyManager (特権)</string>
<string name="dsds">デュアル SIM</string>
<string name="toast_dsds_switched">DSDS の状態が切り替わりました。モデムが再起動するまでお待ちください。</string>

View file

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="no_euicc_priv">在此设备上找不到 eUICC 芯片。\n在某些设备上您可能需要先在此应用的菜单中启用双卡支持。</string>
<string name="no_euicc">在此设备上找不到 eUICC 芯片。\n在某些设备上您可能需要先在此应用的菜单中启用双卡支持。</string>
<string name="dsds">双卡</string>
<string name="toast_dsds_switched">双卡支持状态已切换。请等待基带重新启动。</string>
<string name="footer_mep">此卡槽支持多个启用配置文件 (MEP)。要启用或禁用此功能,请使用\"卡槽映射\"工具。</string>

View file

@ -1,6 +1,6 @@
<resources>
<string name="app_name" translatable="false">OpenEUICC</string>
<string name="no_euicc_priv">No eUICC found on this device.\nOn some devices, you may need to enable dual SIM first in the menu of this app.</string>
<string name="no_euicc">No eUICC found on this device.\nOn some devices, you may need to enable dual SIM first in the menu of this app.</string>
<string name="telephony_manager">TelephonyManager (Privileged)</string>
<string name="dsds">Dual SIM</string>