forked from PeterCxy/OpenEUICC
Compare commits
No commits in common. "9d18253e44574a4cf518523a339e4cada2f3f57c" and "aed247904487ba9b2f01ac2030406a9bd0c1c37c" have entirely different histories.
9d18253e44
...
aed2479044
32 changed files with 183 additions and 263 deletions
|
@ -37,7 +37,6 @@ open class DefaultEuiccChannelFactory(protected val context: Context) : EuiccCha
|
||||||
return EuiccChannelImpl(
|
return EuiccChannelImpl(
|
||||||
context.getString(R.string.omapi),
|
context.getString(R.string.omapi),
|
||||||
port,
|
port,
|
||||||
intrinsicChannelName = null,
|
|
||||||
OmapiApduInterface(
|
OmapiApduInterface(
|
||||||
seService!!,
|
seService!!,
|
||||||
port,
|
port,
|
||||||
|
@ -68,7 +67,6 @@ open class DefaultEuiccChannelFactory(protected val context: Context) : EuiccCha
|
||||||
return EuiccChannelImpl(
|
return EuiccChannelImpl(
|
||||||
context.getString(R.string.usb),
|
context.getString(R.string.usb),
|
||||||
FakeUiccPortInfoCompat(FakeUiccCardInfoCompat(EuiccChannelManager.USB_CHANNEL_ID)),
|
FakeUiccPortInfoCompat(FakeUiccCardInfoCompat(EuiccChannelManager.USB_CHANNEL_ID)),
|
||||||
intrinsicChannelName = usbDevice.productName,
|
|
||||||
UsbApduInterface(
|
UsbApduInterface(
|
||||||
conn,
|
conn,
|
||||||
bulkIn,
|
bulkIn,
|
||||||
|
|
|
@ -16,12 +16,5 @@ interface EuiccChannel {
|
||||||
|
|
||||||
val valid: Boolean
|
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()
|
fun close()
|
||||||
}
|
}
|
|
@ -10,7 +10,6 @@ import net.typeblog.lpac_jni.impl.LocalProfileAssistantImpl
|
||||||
class EuiccChannelImpl(
|
class EuiccChannelImpl(
|
||||||
override val type: String,
|
override val type: String,
|
||||||
override val port: UiccPortInfoCompat,
|
override val port: UiccPortInfoCompat,
|
||||||
override val intrinsicChannelName: String?,
|
|
||||||
apduInterface: ApduInterface,
|
apduInterface: ApduInterface,
|
||||||
verboseLoggingFlow: Flow<Boolean>,
|
verboseLoggingFlow: Flow<Boolean>,
|
||||||
ignoreTLSCertificateFlow: Flow<Boolean>
|
ignoreTLSCertificateFlow: Flow<Boolean>
|
||||||
|
|
|
@ -31,8 +31,6 @@ class EuiccChannelWrapper(orig: EuiccChannel) : EuiccChannel {
|
||||||
override val lpa: LocalProfileAssistant by lpaDelegate
|
override val lpa: LocalProfileAssistant by lpaDelegate
|
||||||
override val valid: Boolean
|
override val valid: Boolean
|
||||||
get() = channel.valid
|
get() = channel.valid
|
||||||
override val intrinsicChannelName: String?
|
|
||||||
get() = channel.intrinsicChannelName
|
|
||||||
|
|
||||||
override fun close() = channel.close()
|
override fun close() = channel.close()
|
||||||
|
|
||||||
|
|
|
@ -15,5 +15,4 @@ interface AppContainer {
|
||||||
val preferenceRepository: PreferenceRepository
|
val preferenceRepository: PreferenceRepository
|
||||||
val uiComponentFactory: UiComponentFactory
|
val uiComponentFactory: UiComponentFactory
|
||||||
val euiccChannelFactory: EuiccChannelFactory
|
val euiccChannelFactory: EuiccChannelFactory
|
||||||
val customizableTextProvider: CustomizableTextProvider
|
|
||||||
}
|
}
|
|
@ -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
|
|
||||||
}
|
|
|
@ -38,8 +38,4 @@ open class DefaultAppContainer(context: Context) : AppContainer {
|
||||||
override val euiccChannelFactory by lazy {
|
override val euiccChannelFactory by lazy {
|
||||||
DefaultEuiccChannelFactory(context)
|
DefaultEuiccChannelFactory(context)
|
||||||
}
|
}
|
||||||
|
|
||||||
override val customizableTextProvider by lazy {
|
|
||||||
DefaultCustomizableTextProvider(context)
|
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -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)
|
|
||||||
}
|
|
|
@ -1,18 +1,13 @@
|
||||||
package im.angry.openeuicc.ui
|
package im.angry.openeuicc.ui
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.content.ClipData
|
|
||||||
import android.content.ClipboardManager
|
|
||||||
import android.os.Build
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import android.widget.Toast
|
|
||||||
import androidx.activity.enableEdgeToEdge
|
import androidx.activity.enableEdgeToEdge
|
||||||
import androidx.annotation.StringRes
|
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.recyclerview.widget.DividerItemDecoration
|
import androidx.recyclerview.widget.DividerItemDecoration
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
|
@ -37,13 +32,6 @@ class EuiccInfoActivity : BaseEuiccAccessActivity() {
|
||||||
|
|
||||||
private var logicalSlotId: Int = -1
|
private var logicalSlotId: Int = -1
|
||||||
|
|
||||||
data class Item(
|
|
||||||
@StringRes
|
|
||||||
val titleResId: Int,
|
|
||||||
val content: String?,
|
|
||||||
val copiedToastResId: Int? = null
|
|
||||||
)
|
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
enableEdgeToEdge()
|
enableEdgeToEdge()
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
@ -53,11 +41,12 @@ class EuiccInfoActivity : BaseEuiccAccessActivity() {
|
||||||
supportActionBar!!.setDisplayHomeAsUpEnabled(true)
|
supportActionBar!!.setDisplayHomeAsUpEnabled(true)
|
||||||
|
|
||||||
swipeRefresh = requireViewById(R.id.swipe_refresh)
|
swipeRefresh = requireViewById(R.id.swipe_refresh)
|
||||||
infoList = requireViewById<RecyclerView>(R.id.recycler_view).also {
|
infoList = requireViewById(R.id.recycler_view)
|
||||||
it.layoutManager = LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false)
|
|
||||||
it.addItemDecoration(DividerItemDecoration(this, LinearLayoutManager.VERTICAL))
|
infoList.layoutManager =
|
||||||
it.adapter = EuiccInfoAdapter()
|
LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false)
|
||||||
}
|
infoList.addItemDecoration(DividerItemDecoration(this, LinearLayoutManager.VERTICAL))
|
||||||
|
infoList.adapter = EuiccInfoAdapter()
|
||||||
|
|
||||||
logicalSlotId = intent.getIntExtra("logicalSlotId", 0)
|
logicalSlotId = intent.getIntExtra("logicalSlotId", 0)
|
||||||
|
|
||||||
|
@ -92,33 +81,29 @@ class EuiccInfoActivity : BaseEuiccAccessActivity() {
|
||||||
|
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
(infoList.adapter!! as EuiccInfoAdapter).euiccInfoItems =
|
(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
|
swipeRefresh.isRefreshing = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun buildEuiccInfoItems(channel: EuiccChannel) = buildList {
|
private fun buildPairs(channel: EuiccChannel) = buildList {
|
||||||
add(Item(R.string.euicc_info_access_mode, channel.type))
|
add(Pair(R.string.euicc_info_access_mode, channel.type))
|
||||||
add(
|
add(
|
||||||
Item(
|
Pair(
|
||||||
R.string.euicc_info_removable,
|
R.string.euicc_info_removable,
|
||||||
formatByBoolean(channel.port.card.isRemovable, YES_NO)
|
formatByBoolean(channel.port.card.isRemovable, YES_NO)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
add(
|
add(Pair(R.string.euicc_info_eid, channel.lpa.eID))
|
||||||
Item(
|
|
||||||
R.string.euicc_info_eid,
|
|
||||||
channel.lpa.eID,
|
|
||||||
copiedToastResId = R.string.toast_eid_copied
|
|
||||||
)
|
|
||||||
)
|
|
||||||
channel.lpa.euiccInfo2.let { info ->
|
channel.lpa.euiccInfo2.let { info ->
|
||||||
add(Item(R.string.euicc_info_firmware_version, info?.euiccFirmwareVersion))
|
add(Pair(R.string.euicc_info_firmware_version, info?.euiccFirmwareVersion))
|
||||||
add(Item(R.string.euicc_info_globalplatform_version, info?.globalPlatformVersion))
|
add(Pair(R.string.euicc_info_globalplatform_version, info?.globalPlatformVersion))
|
||||||
add(Item(R.string.euicc_info_pp_version, info?.ppVersion))
|
add(Pair(R.string.euicc_info_pp_version, info?.ppVersion))
|
||||||
add(Item(R.string.euicc_info_sas_accreditation_number, info?.sasAccreditationNumber))
|
add(Pair(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_free_nvram, info?.freeNvram?.let(::formatFreeSpace)))
|
||||||
}
|
}
|
||||||
channel.lpa.euiccInfo2?.euiccCiPKIdListForSigning.orEmpty().let { signers ->
|
channel.lpa.euiccInfo2?.euiccCiPKIdListForSigning.orEmpty().let { signers ->
|
||||||
// SGP.28 v1.0, eSIM CI Registration Criteria (Page 5 of 9, 2019-10-24)
|
// 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
|
PKID_GSMA_TEST_CI.any(signers::contains) -> R.string.euicc_info_ci_gsma_test
|
||||||
else -> R.string.euicc_info_ci_unknown
|
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) {
|
inner class EuiccInfoViewHolder(root: View) : ViewHolder(root) {
|
||||||
private val title: TextView = root.requireViewById(R.id.euicc_info_title)
|
private val title: TextView = root.requireViewById(R.id.euicc_info_title)
|
||||||
private val content: TextView = root.requireViewById(R.id.euicc_info_content)
|
private val content: TextView = root.requireViewById(R.id.euicc_info_content)
|
||||||
private var copiedToastResId: Int? = null
|
|
||||||
|
|
||||||
init {
|
fun bind(item: Pair<String, String>) {
|
||||||
root.setOnClickListener {
|
title.text = item.first
|
||||||
if (copiedToastResId != null) {
|
content.text = item.second
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
inner class EuiccInfoAdapter : RecyclerView.Adapter<EuiccInfoViewHolder>() {
|
inner class EuiccInfoAdapter : RecyclerView.Adapter<EuiccInfoViewHolder>() {
|
||||||
var euiccInfoItems: List<Item> = listOf()
|
var euiccInfoItems: List<Pair<String, String>> = listOf()
|
||||||
@SuppressLint("NotifyDataSetChanged")
|
@SuppressLint("NotifyDataSetChanged")
|
||||||
set(newVal) {
|
set(newVal) {
|
||||||
field = newVal
|
field = newVal
|
||||||
|
|
|
@ -4,7 +4,6 @@ import android.annotation.SuppressLint
|
||||||
import android.content.ClipData
|
import android.content.ClipData
|
||||||
import android.content.ClipboardManager
|
import android.content.ClipboardManager
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.Build
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.text.method.PasswordTransformationMethod
|
import android.text.method.PasswordTransformationMethod
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
|
@ -262,7 +261,7 @@ open class EuiccManagementFragment : Fragment(), EuiccProfilesChangedListener,
|
||||||
invalid = true
|
invalid = true
|
||||||
// Timed out waiting for SIM to come back online, we can no longer assume that the LPA is still valid
|
// Timed out waiting for SIM to come back online, we can no longer assume that the LPA is still valid
|
||||||
AlertDialog.Builder(requireContext()).apply {
|
AlertDialog.Builder(requireContext()).apply {
|
||||||
setMessage(appContainer.customizableTextProvider.profileSwitchingTimeoutMessage)
|
setMessage(R.string.enable_disable_timeout)
|
||||||
setPositiveButton(android.R.string.ok) { dialog, _ ->
|
setPositiveButton(android.R.string.ok) { dialog, _ ->
|
||||||
dialog.dismiss()
|
dialog.dismiss()
|
||||||
requireActivity().finish()
|
requireActivity().finish()
|
||||||
|
@ -349,8 +348,7 @@ open class EuiccManagementFragment : Fragment(), EuiccProfilesChangedListener,
|
||||||
iccid.setOnLongClickListener {
|
iccid.setOnLongClickListener {
|
||||||
requireContext().getSystemService(ClipboardManager::class.java)!!
|
requireContext().getSystemService(ClipboardManager::class.java)!!
|
||||||
.setPrimaryClip(ClipData.newPlainText("iccid", iccid.text))
|
.setPrimaryClip(ClipData.newPlainText("iccid", iccid.text))
|
||||||
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.S_V2) Toast
|
Toast.makeText(requireContext(), R.string.toast_iccid_copied, Toast.LENGTH_SHORT)
|
||||||
.makeText(requireContext(), R.string.toast_iccid_copied, Toast.LENGTH_SHORT)
|
|
||||||
.show()
|
.show()
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,7 @@ import android.view.View
|
||||||
import android.widget.ScrollView
|
import android.widget.ScrollView
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import androidx.activity.enableEdgeToEdge
|
import androidx.activity.enableEdgeToEdge
|
||||||
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
||||||
|
@ -16,6 +17,7 @@ import im.angry.openeuicc.util.*
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
|
import java.io.FileOutputStream
|
||||||
import java.util.Date
|
import java.util.Date
|
||||||
|
|
||||||
class LogsActivity : AppCompatActivity() {
|
class LogsActivity : AppCompatActivity() {
|
||||||
|
@ -25,15 +27,15 @@ class LogsActivity : AppCompatActivity() {
|
||||||
private lateinit var logStr: String
|
private lateinit var logStr: String
|
||||||
|
|
||||||
private val saveLogs =
|
private val saveLogs =
|
||||||
setupLogSaving(
|
registerForActivityResult(ActivityResultContracts.CreateDocument("text/plain")) { uri ->
|
||||||
getLogFileName = {
|
if (uri == null) return@registerForActivityResult
|
||||||
getString(
|
if (!this::logStr.isInitialized) return@registerForActivityResult
|
||||||
R.string.logs_filename_template,
|
contentResolver.openFileDescriptor(uri, "w")?.use {
|
||||||
SimpleDateFormat.getDateTimeInstance().format(Date())
|
FileOutputStream(it.fileDescriptor).use { os ->
|
||||||
)
|
os.write(logStr.encodeToByteArray())
|
||||||
},
|
}
|
||||||
getLogText = { logStr }
|
}
|
||||||
)
|
}
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
enableEdgeToEdge()
|
enableEdgeToEdge()
|
||||||
|
@ -74,7 +76,9 @@ class LogsActivity : AppCompatActivity() {
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
R.id.save -> {
|
R.id.save -> {
|
||||||
saveLogs()
|
saveLogs.launch(getString(R.string.logs_filename_template,
|
||||||
|
SimpleDateFormat.getDateTimeInstance().format(Date())
|
||||||
|
))
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
else -> super.onOptionsItemSelected(item)
|
else -> super.onOptionsItemSelected(item)
|
||||||
|
|
|
@ -163,8 +163,7 @@ open class MainActivity : BaseEuiccAccessActivity(), OpenEuiccContextMarker {
|
||||||
// but it could change in the future
|
// but it could change in the future
|
||||||
euiccChannelManager.notifyEuiccProfilesChanged(channel.logicalSlotId)
|
euiccChannelManager.notifyEuiccProfilesChanged(channel.logicalSlotId)
|
||||||
|
|
||||||
val channelName =
|
val channelName = getString(R.string.channel_name_format, channel.logicalSlotId)
|
||||||
appContainer.customizableTextProvider.formatInternalChannelName(channel.logicalSlotId)
|
|
||||||
newPages.add(Page(channel.logicalSlotId, channelName) {
|
newPages.add(Page(channel.logicalSlotId, channelName) {
|
||||||
appContainer.uiComponentFactory.createEuiccManagementFragment(slotId, portId)
|
appContainer.uiComponentFactory.createEuiccManagementFragment(slotId, portId)
|
||||||
})
|
})
|
||||||
|
|
|
@ -4,20 +4,15 @@ import android.os.Bundle
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.TextView
|
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import im.angry.openeuicc.common.R
|
import im.angry.openeuicc.common.R
|
||||||
import im.angry.openeuicc.util.*
|
|
||||||
|
|
||||||
class NoEuiccPlaceholderFragment : Fragment(), OpenEuiccContextMarker {
|
class NoEuiccPlaceholderFragment : Fragment() {
|
||||||
override fun onCreateView(
|
override fun onCreateView(
|
||||||
inflater: LayoutInflater,
|
inflater: LayoutInflater,
|
||||||
container: ViewGroup?,
|
container: ViewGroup?,
|
||||||
savedInstanceState: Bundle?
|
savedInstanceState: Bundle?
|
||||||
): View? {
|
): View? {
|
||||||
val view = inflater.inflate(R.layout.fragment_no_euicc_placeholder, container, false)
|
return 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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -6,6 +6,7 @@ import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.provider.Settings
|
import android.provider.Settings
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
|
import androidx.datastore.preferences.core.Preferences
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.preference.CheckBoxPreference
|
import androidx.preference.CheckBoxPreference
|
||||||
import androidx.preference.Preference
|
import androidx.preference.Preference
|
||||||
|
@ -13,6 +14,7 @@ import androidx.preference.PreferenceCategory
|
||||||
import androidx.preference.PreferenceFragmentCompat
|
import androidx.preference.PreferenceFragmentCompat
|
||||||
import im.angry.openeuicc.common.R
|
import im.angry.openeuicc.common.R
|
||||||
import im.angry.openeuicc.util.*
|
import im.angry.openeuicc.util.*
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.collect
|
import kotlinx.coroutines.flow.collect
|
||||||
import kotlinx.coroutines.flow.onEach
|
import kotlinx.coroutines.flow.onEach
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
@ -58,25 +60,25 @@ open class SettingsFragment: PreferenceFragmentCompat() {
|
||||||
}
|
}
|
||||||
|
|
||||||
findPreference<CheckBoxPreference>("pref_notifications_download")
|
findPreference<CheckBoxPreference>("pref_notifications_download")
|
||||||
?.bindBooleanFlow(preferenceRepository.notificationDownloadFlow)
|
?.bindBooleanFlow(preferenceRepository.notificationDownloadFlow, PreferenceKeys.NOTIFICATION_DOWNLOAD)
|
||||||
|
|
||||||
findPreference<CheckBoxPreference>("pref_notifications_delete")
|
findPreference<CheckBoxPreference>("pref_notifications_delete")
|
||||||
?.bindBooleanFlow(preferenceRepository.notificationDeleteFlow)
|
?.bindBooleanFlow(preferenceRepository.notificationDeleteFlow, PreferenceKeys.NOTIFICATION_DELETE)
|
||||||
|
|
||||||
findPreference<CheckBoxPreference>("pref_notifications_switch")
|
findPreference<CheckBoxPreference>("pref_notifications_switch")
|
||||||
?.bindBooleanFlow(preferenceRepository.notificationSwitchFlow)
|
?.bindBooleanFlow(preferenceRepository.notificationSwitchFlow, PreferenceKeys.NOTIFICATION_SWITCH)
|
||||||
|
|
||||||
findPreference<CheckBoxPreference>("pref_advanced_disable_safeguard_removable_esim")
|
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")
|
findPreference<CheckBoxPreference>("pref_advanced_verbose_logging")
|
||||||
?.bindBooleanFlow(preferenceRepository.verboseLoggingFlow)
|
?.bindBooleanFlow(preferenceRepository.verboseLoggingFlow, PreferenceKeys.VERBOSE_LOGGING)
|
||||||
|
|
||||||
findPreference<CheckBoxPreference>("pref_developer_unfiltered_profile_list")
|
findPreference<CheckBoxPreference>("pref_developer_unfiltered_profile_list")
|
||||||
?.bindBooleanFlow(preferenceRepository.unfilteredProfileListFlow)
|
?.bindBooleanFlow(preferenceRepository.unfilteredProfileListFlow, PreferenceKeys.UNFILTERED_PROFILE_LIST)
|
||||||
|
|
||||||
findPreference<CheckBoxPreference>("pref_ignore_tls_certificate")
|
findPreference<CheckBoxPreference>("pref_ignore_tls_certificate")
|
||||||
?.bindBooleanFlow(preferenceRepository.ignoreTLSCertificateFlow)
|
?.bindBooleanFlow(preferenceRepository.ignoreTLSCertificateFlow, PreferenceKeys.IGNORE_TLS_CERTIFICATE)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onStart() {
|
override fun onStart() {
|
||||||
|
@ -97,7 +99,10 @@ open class SettingsFragment: PreferenceFragmentCompat() {
|
||||||
|
|
||||||
if (numClicks == 7) {
|
if (numClicks == 7) {
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
preferenceRepository.developerOptionsEnabledFlow.updatePreference(true)
|
preferenceRepository.updatePreference(
|
||||||
|
PreferenceKeys.DEVELOPER_OPTIONS_ENABLED,
|
||||||
|
true
|
||||||
|
)
|
||||||
|
|
||||||
lastToast?.cancel()
|
lastToast?.cancel()
|
||||||
Toast.makeText(
|
Toast.makeText(
|
||||||
|
@ -119,14 +124,14 @@ open class SettingsFragment: PreferenceFragmentCompat() {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun CheckBoxPreference.bindBooleanFlow(flow: PreferenceFlowWrapper<Boolean>) {
|
private fun CheckBoxPreference.bindBooleanFlow(flow: Flow<Boolean>, key: Preferences.Key<Boolean>) {
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
flow.collect { isChecked = it }
|
flow.collect { isChecked = it }
|
||||||
}
|
}
|
||||||
|
|
||||||
setOnPreferenceChangeListener { _, newValue ->
|
setOnPreferenceChangeListener { _, newValue ->
|
||||||
runBlocking {
|
runBlocking {
|
||||||
flow.updatePreference(newValue as Boolean)
|
preferenceRepository.updatePreference(key, newValue as Boolean)
|
||||||
}
|
}
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
|
@ -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()
|
||||||
|
}
|
||||||
|
}
|
|
@ -6,8 +6,10 @@ import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
import im.angry.openeuicc.common.R
|
import im.angry.openeuicc.common.R
|
||||||
import im.angry.openeuicc.util.*
|
import im.angry.openeuicc.util.*
|
||||||
|
import java.io.FileOutputStream
|
||||||
import java.util.Date
|
import java.util.Date
|
||||||
|
|
||||||
class DownloadWizardDiagnosticsFragment : DownloadWizardActivity.DownloadWizardStepFragment() {
|
class DownloadWizardDiagnosticsFragment : DownloadWizardActivity.DownloadWizardStepFragment() {
|
||||||
|
@ -19,15 +21,14 @@ class DownloadWizardDiagnosticsFragment : DownloadWizardActivity.DownloadWizardS
|
||||||
private lateinit var diagnosticTextView: TextView
|
private lateinit var diagnosticTextView: TextView
|
||||||
|
|
||||||
private val saveDiagnostics =
|
private val saveDiagnostics =
|
||||||
setupLogSaving(
|
registerForActivityResult(ActivityResultContracts.CreateDocument("text/plain")) { uri ->
|
||||||
getLogFileName = {
|
if (uri == null) return@registerForActivityResult
|
||||||
getString(
|
requireActivity().contentResolver.openFileDescriptor(uri, "w")?.use {
|
||||||
R.string.download_wizard_diagnostics_file_template,
|
FileOutputStream(it.fileDescriptor).use { os ->
|
||||||
SimpleDateFormat.getDateTimeInstance().format(Date())
|
os.write(diagnosticTextView.text.toString().encodeToByteArray())
|
||||||
)
|
}
|
||||||
},
|
}
|
||||||
getLogText = { diagnosticTextView.text.toString() }
|
}
|
||||||
)
|
|
||||||
|
|
||||||
override fun createNextFragment(): DownloadWizardActivity.DownloadWizardStepFragment? = null
|
override fun createNextFragment(): DownloadWizardActivity.DownloadWizardStepFragment? = null
|
||||||
|
|
||||||
|
@ -40,7 +41,12 @@ class DownloadWizardDiagnosticsFragment : DownloadWizardActivity.DownloadWizardS
|
||||||
): View? {
|
): View? {
|
||||||
val view = inflater.inflate(R.layout.fragment_download_diagnostics, container, false)
|
val view = inflater.inflate(R.layout.fragment_download_diagnostics, container, false)
|
||||||
view.requireViewById<View>(R.id.download_wizard_diagnostics_save).setOnClickListener {
|
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)
|
diagnosticTextView = view.requireViewById(R.id.download_wizard_diagnostics_text)
|
||||||
return view
|
return view
|
||||||
|
|
|
@ -35,8 +35,7 @@ class DownloadWizardSlotSelectFragment : DownloadWizardActivity.DownloadWizardSt
|
||||||
val eID: String,
|
val eID: String,
|
||||||
val freeSpace: Int,
|
val freeSpace: Int,
|
||||||
val imei: String,
|
val imei: String,
|
||||||
val enabledProfileName: String?,
|
val enabledProfileName: String?
|
||||||
val intrinsicChannelName: String?,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
private var loaded = false
|
private var loaded = false
|
||||||
|
@ -62,9 +61,7 @@ class DownloadWizardSlotSelectFragment : DownloadWizardActivity.DownloadWizardSt
|
||||||
setMessage(R.string.profile_download_low_nvram_message)
|
setMessage(R.string.profile_download_low_nvram_message)
|
||||||
setCancelable(true)
|
setCancelable(true)
|
||||||
setPositiveButton(android.R.string.ok, null)
|
setPositiveButton(android.R.string.ok, null)
|
||||||
setNegativeButton(android.R.string.cancel) { _, _ ->
|
setNegativeButton(android.R.string.cancel, null)
|
||||||
requireActivity().finish()
|
|
||||||
}
|
|
||||||
show()
|
show()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -109,8 +106,7 @@ class DownloadWizardSlotSelectFragment : DownloadWizardActivity.DownloadWizardSt
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
""
|
""
|
||||||
},
|
},
|
||||||
channel.lpa.profiles.find { it.state == LocalProfileInfo.State.Enabled }?.displayName,
|
channel.lpa.profiles.find { it.state == LocalProfileInfo.State.Enabled }?.displayName
|
||||||
channel.intrinsicChannelName,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}.toList().sortedBy { it.logicalSlotId }
|
}.toList().sortedBy { it.logicalSlotId }
|
||||||
|
@ -181,9 +177,9 @@ class DownloadWizardSlotSelectFragment : DownloadWizardActivity.DownloadWizardSt
|
||||||
}
|
}
|
||||||
|
|
||||||
title.text = if (item.logicalSlotId == EuiccChannelManager.USB_CHANNEL_ID) {
|
title.text = if (item.logicalSlotId == EuiccChannelManager.USB_CHANNEL_ID) {
|
||||||
item.intrinsicChannelName ?: root.context.getString(R.string.usb)
|
root.context.getString(R.string.usb)
|
||||||
} else {
|
} else {
|
||||||
appContainer.customizableTextProvider.formatInternalChannelName(item.logicalSlotId)
|
root.context.getString(R.string.download_wizard_slot_title, item.logicalSlotId)
|
||||||
}
|
}
|
||||||
eID.text = item.eID
|
eID.text = item.eID
|
||||||
activeProfile.text = item.enabledProfileName ?: root.context.getString(R.string.unknown)
|
activeProfile.text = item.enabledProfileName ?: root.context.getString(R.string.unknown)
|
||||||
|
|
|
@ -19,7 +19,7 @@ val Context.preferenceRepository: PreferenceRepository
|
||||||
val Fragment.preferenceRepository: PreferenceRepository
|
val Fragment.preferenceRepository: PreferenceRepository
|
||||||
get() = requireContext().preferenceRepository
|
get() = requireContext().preferenceRepository
|
||||||
|
|
||||||
internal object PreferenceKeys {
|
object PreferenceKeys {
|
||||||
// ---- Profile Notifications ----
|
// ---- Profile Notifications ----
|
||||||
val NOTIFICATION_DOWNLOAD = booleanPreferencesKey("notification_download")
|
val NOTIFICATION_DOWNLOAD = booleanPreferencesKey("notification_download")
|
||||||
val NOTIFICATION_DELETE = booleanPreferencesKey("notification_delete")
|
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 unfilteredProfileListFlow = bindFlow(PreferenceKeys.UNFILTERED_PROFILE_LIST, false)
|
||||||
val ignoreTLSCertificateFlow = bindFlow(PreferenceKeys.IGNORE_TLS_CERTIFICATE, false)
|
val ignoreTLSCertificateFlow = bindFlow(PreferenceKeys.IGNORE_TLS_CERTIFICATE, false)
|
||||||
|
|
||||||
private fun <T> bindFlow(key: Preferences.Key<T>, defaultValue: T): PreferenceFlowWrapper<T> =
|
private fun <T> bindFlow(key: Preferences.Key<T>, defaultValue: T): Flow<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,
|
|
||||||
context.dataStore.data.map { it[key] ?: defaultValue }
|
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 }
|
context.dataStore.edit { it[key] = value }
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -1,23 +1,17 @@
|
||||||
package im.angry.openeuicc.util
|
package im.angry.openeuicc.util
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.content.Intent
|
|
||||||
import android.content.res.Resources
|
import android.content.res.Resources
|
||||||
import android.graphics.Rect
|
import android.graphics.Rect
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
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.app.AppCompatActivity
|
||||||
|
import androidx.appcompat.widget.Toolbar
|
||||||
import androidx.core.view.ViewCompat
|
import androidx.core.view.ViewCompat
|
||||||
import androidx.core.view.WindowInsetsCompat
|
import androidx.core.view.WindowInsetsCompat
|
||||||
import androidx.core.view.updateLayoutParams
|
import androidx.core.view.updateLayoutParams
|
||||||
import androidx.core.view.updatePadding
|
import androidx.core.view.updatePadding
|
||||||
import androidx.fragment.app.DialogFragment
|
import androidx.fragment.app.DialogFragment
|
||||||
import androidx.fragment.app.Fragment
|
|
||||||
import im.angry.openeuicc.common.R
|
import im.angry.openeuicc.common.R
|
||||||
import java.io.FileOutputStream
|
|
||||||
|
|
||||||
// Source: <https://stackoverflow.com/questions/12478520/how-to-set-dialogfragments-width-and-height>
|
// Source: <https://stackoverflow.com/questions/12478520/how-to-set-dialogfragments-width-and-height>
|
||||||
/**
|
/**
|
||||||
|
@ -75,44 +69,4 @@ fun setupRootViewInsets(view: ViewGroup) {
|
||||||
|
|
||||||
WindowInsetsCompat.CONSUMED
|
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())
|
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -43,6 +43,7 @@
|
||||||
<string name="download_wizard_back">戻る</string>
|
<string name="download_wizard_back">戻る</string>
|
||||||
<string name="download_wizard_next">次へ</string>
|
<string name="download_wizard_next">次へ</string>
|
||||||
<string name="download_wizard_slot_select">ダウンロードする eSIM を選択または確認:</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">タイプ:</string>
|
||||||
<string name="download_wizard_slot_type_removable">リムーバブル</string>
|
<string name="download_wizard_slot_type_removable">リムーバブル</string>
|
||||||
<string name="download_wizard_slot_type_internal">内部</string>
|
<string name="download_wizard_slot_type_internal">内部</string>
|
||||||
|
|
|
@ -81,6 +81,7 @@
|
||||||
<string name="download_wizard_back">返回</string>
|
<string name="download_wizard_back">返回</string>
|
||||||
<string name="download_wizard_next">下一步</string>
|
<string name="download_wizard_next">下一步</string>
|
||||||
<string name="download_wizard_slot_select">请选择或确认下载目标 eSIM 卡槽:</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">类型:</string>
|
||||||
<string name="download_wizard_slot_type_removable">可插拔</string>
|
<string name="download_wizard_slot_type_removable">可插拔</string>
|
||||||
<string name="download_wizard_slot_type_internal">内置</string>
|
<string name="download_wizard_slot_type_internal">内置</string>
|
||||||
|
|
|
@ -31,7 +31,6 @@
|
||||||
<string name="toast_profile_name_too_long">Nickname cannot be longer than 64 characters</string>
|
<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_profile_delete_confirm_text_mismatched">Confirmation string mismatch</string>
|
||||||
<string name="toast_iccid_copied">ICCID copied to clipboard</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 Slot</string>
|
||||||
<string name="slot_select_select">Select</string>
|
<string name="slot_select_select">Select</string>
|
||||||
|
@ -65,6 +64,7 @@
|
||||||
<string name="download_wizard_back">Back</string>
|
<string name="download_wizard_back">Back</string>
|
||||||
<string name="download_wizard_next">Next</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_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">Type:</string>
|
||||||
<string name="download_wizard_slot_type_removable">Removable</string>
|
<string name="download_wizard_slot_type_removable">Removable</string>
|
||||||
<string name="download_wizard_slot_type_internal">Internal</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_save">Save</string>
|
||||||
<string name="download_wizard_diagnostics_file_template">Diagnostics at %s</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_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>
|
<string name="profile_delete_confirm">Are you sure you want to delete the profile %s? This operation is irreversible.</string>
|
||||||
|
|
|
@ -6,8 +6,4 @@ class UnprivilegedAppContainer(context: Context) : DefaultAppContainer(context)
|
||||||
override val uiComponentFactory by lazy {
|
override val uiComponentFactory by lazy {
|
||||||
UnprivilegedUiComponentFactory()
|
UnprivilegedUiComponentFactory()
|
||||||
}
|
}
|
||||||
|
|
||||||
override val customizableTextProvider by lazy {
|
|
||||||
UnprivilegedCustomizableTextProvider(context)
|
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -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)
|
|
||||||
}
|
|
|
@ -3,7 +3,6 @@ package im.angry.openeuicc.ui
|
||||||
import android.content.ClipData
|
import android.content.ClipData
|
||||||
import android.content.ClipboardManager
|
import android.content.ClipboardManager
|
||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
import android.os.Build
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.preference.Preference
|
import androidx.preference.Preference
|
||||||
|
@ -36,8 +35,7 @@ class UnprivilegedSettingsFragment : SettingsFragment() {
|
||||||
setOnPreferenceClickListener {
|
setOnPreferenceClickListener {
|
||||||
requireContext().getSystemService(ClipboardManager::class.java)!!
|
requireContext().getSystemService(ClipboardManager::class.java)!!
|
||||||
.setPrimaryClip(ClipData.newPlainText("ara-m", summary))
|
.setPrimaryClip(ClipData.newPlainText("ara-m", summary))
|
||||||
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.S_V2) Toast
|
Toast.makeText(requireContext(), R.string.toast_ara_m_copied, Toast.LENGTH_SHORT)
|
||||||
.makeText(requireContext(), R.string.toast_ara_m_copied, Toast.LENGTH_SHORT)
|
|
||||||
.show()
|
.show()
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<resources>
|
<resources>
|
||||||
<string name="app_name" translatable="false">EasyEUICC</string>
|
<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="compatibility_check">Compatibility Check</string>
|
||||||
<string name="open_sim_toolkit">Open SIM Toolkit</string>
|
<string name="open_sim_toolkit">Open SIM Toolkit</string>
|
||||||
|
|
||||||
|
|
|
@ -30,7 +30,6 @@ class PrivilegedEuiccChannelFactory(context: Context) : DefaultEuiccChannelFacto
|
||||||
return EuiccChannelImpl(
|
return EuiccChannelImpl(
|
||||||
context.getString(R.string.telephony_manager),
|
context.getString(R.string.telephony_manager),
|
||||||
port,
|
port,
|
||||||
intrinsicChannelName = null,
|
|
||||||
TelephonyManagerApduInterface(
|
TelephonyManagerApduInterface(
|
||||||
port,
|
port,
|
||||||
tm,
|
tm,
|
||||||
|
|
|
@ -23,8 +23,4 @@ class PrivilegedAppContainer(context: Context) : DefaultAppContainer(context) {
|
||||||
override val euiccChannelFactory by lazy {
|
override val euiccChannelFactory by lazy {
|
||||||
PrivilegedEuiccChannelFactory(context)
|
PrivilegedEuiccChannelFactory(context)
|
||||||
}
|
}
|
||||||
|
|
||||||
override val customizableTextProvider by lazy {
|
|
||||||
PrivilegedCustomizableTextProvider(context)
|
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -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)
|
|
||||||
}
|
|
|
@ -1,6 +1,6 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<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="telephony_manager">TelephonyManager (特権)</string>
|
||||||
<string name="dsds">デュアル SIM</string>
|
<string name="dsds">デュアル SIM</string>
|
||||||
<string name="toast_dsds_switched">DSDS の状態が切り替わりました。モデムが再起動するまでお待ちください。</string>
|
<string name="toast_dsds_switched">DSDS の状態が切り替わりました。モデムが再起動するまでお待ちください。</string>
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
<string name="no_euicc_priv">在此设备上找不到 eUICC 芯片。\n在某些设备上,您可能需要先在此应用的菜单中启用双卡支持。</string>
|
<string name="no_euicc">在此设备上找不到 eUICC 芯片。\n在某些设备上,您可能需要先在此应用的菜单中启用双卡支持。</string>
|
||||||
<string name="dsds">双卡</string>
|
<string name="dsds">双卡</string>
|
||||||
<string name="toast_dsds_switched">双卡支持状态已切换。请等待基带重新启动。</string>
|
<string name="toast_dsds_switched">双卡支持状态已切换。请等待基带重新启动。</string>
|
||||||
<string name="footer_mep">此卡槽支持多个启用配置文件 (MEP)。要启用或禁用此功能,请使用\"卡槽映射\"工具。</string>
|
<string name="footer_mep">此卡槽支持多个启用配置文件 (MEP)。要启用或禁用此功能,请使用\"卡槽映射\"工具。</string>
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<resources>
|
<resources>
|
||||||
<string name="app_name" translatable="false">OpenEUICC</string>
|
<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="telephony_manager">TelephonyManager (Privileged)</string>
|
||||||
|
|
||||||
<string name="dsds">Dual SIM</string>
|
<string name="dsds">Dual SIM</string>
|
||||||
|
|
Loading…
Add table
Reference in a new issue