forked from PeterCxy/OpenEUICC
Compare commits
18 commits
1140ddb249
...
125f1da6af
Author | SHA1 | Date | |
---|---|---|---|
125f1da6af | |||
4a482b9c73 | |||
ca46b578f7 | |||
23022b14be | |||
2d66c1f334 | |||
09b98b37ab | |||
fdbf9b3252 | |||
84f47cb0f0 | |||
0229ef41df | |||
15d3b701a5 | |||
700578a369 | |||
eab60bf3d3 | |||
5b80afd5fe | |||
400c2ff9f9 | |||
b4f562f90b | |||
5a000278d3 | |||
38d38523f9 | |||
022ca1da9d |
38 changed files with 438 additions and 618 deletions
|
@ -19,11 +19,6 @@
|
|||
android:name="im.angry.openeuicc.ui.NotificationsActivity"
|
||||
android:label="@string/profile_notifications" />
|
||||
|
||||
<activity
|
||||
android:name="im.angry.openeuicc.ui.DirectProfileDownloadActivity"
|
||||
android:label="@string/profile_download"
|
||||
android:theme="@style/Theme.AppCompat.Translucent" />
|
||||
|
||||
<activity
|
||||
android:name="im.angry.openeuicc.ui.EuiccInfoActivity"
|
||||
android:label="@string/euicc_info" />
|
||||
|
@ -33,9 +28,15 @@
|
|||
android:label="@string/pref_advanced_logs" />
|
||||
|
||||
<activity
|
||||
android:exported="true"
|
||||
android:name="im.angry.openeuicc.ui.wizard.DownloadWizardActivity"
|
||||
android:label="@string/download_wizard" />
|
||||
|
||||
<activity-alias
|
||||
android:exported="true"
|
||||
android:name="im.angry.openeuicc.ui.DirectProfileDownloadActivity"
|
||||
android:targetActivity="im.angry.openeuicc.ui.wizard.DownloadWizardActivity" />
|
||||
|
||||
<activity
|
||||
android:name="com.journeyapps.barcodescanner.CaptureActivity"
|
||||
android:screenOrientation="fullSensor"
|
||||
|
|
|
@ -1,12 +1,16 @@
|
|||
package im.angry.openeuicc.di
|
||||
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.preference.PreferenceFragmentCompat
|
||||
import im.angry.openeuicc.ui.EuiccManagementFragment
|
||||
import im.angry.openeuicc.ui.NoEuiccPlaceholderFragment
|
||||
import im.angry.openeuicc.ui.SettingsFragment
|
||||
|
||||
open class DefaultUiComponentFactory : UiComponentFactory {
|
||||
override fun createEuiccManagementFragment(slotId: Int, portId: Int): EuiccManagementFragment =
|
||||
EuiccManagementFragment.newInstance(slotId, portId)
|
||||
|
||||
override fun createNoEuiccPlaceholderFragment(): Fragment = NoEuiccPlaceholderFragment()
|
||||
|
||||
override fun createSettingsFragment(): Fragment = SettingsFragment()
|
||||
}
|
|
@ -1,9 +1,11 @@
|
|||
package im.angry.openeuicc.di
|
||||
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.preference.PreferenceFragmentCompat
|
||||
import im.angry.openeuicc.ui.EuiccManagementFragment
|
||||
|
||||
interface UiComponentFactory {
|
||||
fun createEuiccManagementFragment(slotId: Int, portId: Int): EuiccManagementFragment
|
||||
fun createNoEuiccPlaceholderFragment(): Fragment
|
||||
fun createSettingsFragment(): Fragment
|
||||
}
|
|
@ -1,52 +0,0 @@
|
|||
package im.angry.openeuicc.ui
|
||||
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import im.angry.openeuicc.util.*
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.toList
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
class DirectProfileDownloadActivity : BaseEuiccAccessActivity(), SlotSelectFragment.SlotSelectedListener, OpenEuiccContextMarker {
|
||||
override fun onInit() {
|
||||
lifecycleScope.launch {
|
||||
val knownChannels = withContext(Dispatchers.IO) {
|
||||
euiccChannelManager.flowEuiccPorts().map { (slotId, portId) ->
|
||||
euiccChannelManager.withEuiccChannel(slotId, portId) { channel ->
|
||||
Triple(slotId, channel.logicalSlotId, portId)
|
||||
}
|
||||
}.toList().sortedBy { it.second }
|
||||
}
|
||||
|
||||
when {
|
||||
knownChannels.isEmpty() -> {
|
||||
finish()
|
||||
}
|
||||
// Detect multiple eUICC chips
|
||||
knownChannels.distinctBy { it.first }.size > 1 -> {
|
||||
SlotSelectFragment.newInstance(
|
||||
knownChannels.map { it.first },
|
||||
knownChannels.map { it.second },
|
||||
knownChannels.map { it.third })
|
||||
.show(supportFragmentManager, SlotSelectFragment.TAG)
|
||||
}
|
||||
else -> {
|
||||
// If the device has only one eSIM "chip" (but may be mapped to multiple slots),
|
||||
// we can skip the slot selection dialog since there is only one chip to save to.
|
||||
onSlotSelected(
|
||||
knownChannels[0].first,
|
||||
knownChannels[0].third
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onSlotSelected(slotId: Int, portId: Int) {
|
||||
ProfileDownloadFragment.newInstance(slotId, portId, finishWhenDone = true)
|
||||
.show(supportFragmentManager, ProfileDownloadFragment.TAG)
|
||||
}
|
||||
|
||||
override fun onSlotSelectCancelled() = finish()
|
||||
}
|
|
@ -37,7 +37,6 @@ import im.angry.openeuicc.util.*
|
|||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.TimeoutCancellationException
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
|
@ -110,16 +109,9 @@ open class EuiccManagementFragment : Fragment(), EuiccProfilesChangedListener,
|
|||
LinearLayoutManager(view.context, LinearLayoutManager.VERTICAL, false)
|
||||
|
||||
fab.setOnClickListener {
|
||||
lifecycleScope.launch {
|
||||
if (preferenceRepository.experimentalDownloadWizardFlow.first()) {
|
||||
Intent(requireContext(), DownloadWizardActivity::class.java).apply {
|
||||
putExtra("selectedLogicalSlot", logicalSlotId)
|
||||
startActivity(this)
|
||||
}
|
||||
} else {
|
||||
ProfileDownloadFragment.newInstance(slotId, portId)
|
||||
.show(childFragmentManager, ProfileDownloadFragment.TAG)
|
||||
}
|
||||
Intent(requireContext(), DownloadWizardActivity::class.java).apply {
|
||||
putExtra("selectedLogicalSlot", logicalSlotId)
|
||||
startActivity(this)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -126,7 +126,10 @@ open class MainActivity : BaseEuiccAccessActivity(), OpenEuiccContextMarker {
|
|||
}
|
||||
|
||||
private fun ensureNotificationPermissions() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU && checkSelfPermission(android.Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) {
|
||||
val needsNotificationPerms = Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU;
|
||||
val notificationPermsGranted =
|
||||
needsNotificationPerms && checkSelfPermission(android.Manifest.permission.POST_NOTIFICATIONS) == PackageManager.PERMISSION_GRANTED
|
||||
if (needsNotificationPerms && !notificationPermsGranted) {
|
||||
requestPermissions(
|
||||
arrayOf(android.Manifest.permission.POST_NOTIFICATIONS),
|
||||
PERMISSION_REQUEST_CODE
|
||||
|
@ -160,38 +163,29 @@ open class MainActivity : BaseEuiccAccessActivity(), OpenEuiccContextMarker {
|
|||
// but it could change in the future
|
||||
euiccChannelManager.notifyEuiccProfilesChanged(channel.logicalSlotId)
|
||||
|
||||
newPages.add(
|
||||
Page(
|
||||
channel.logicalSlotId,
|
||||
getString(R.string.channel_name_format, channel.logicalSlotId)
|
||||
) {
|
||||
appContainer.uiComponentFactory.createEuiccManagementFragment(
|
||||
slotId,
|
||||
portId
|
||||
)
|
||||
})
|
||||
val channelName = getString(R.string.channel_name_format, channel.logicalSlotId)
|
||||
newPages.add(Page(channel.logicalSlotId, channelName) {
|
||||
appContainer.uiComponentFactory.createEuiccManagementFragment(slotId, portId)
|
||||
})
|
||||
}
|
||||
}.collect()
|
||||
|
||||
// If USB readers exist, add them at the very last
|
||||
// We use a wrapper fragment to handle logic specific to USB readers
|
||||
usbDevice?.let {
|
||||
newPages.add(
|
||||
Page(
|
||||
EuiccChannelManager.USB_CHANNEL_ID,
|
||||
it.productName ?: getString(R.string.usb)
|
||||
) { UsbCcidReaderFragment() })
|
||||
val productName = it.productName ?: getString(R.string.usb)
|
||||
newPages.add(Page(EuiccChannelManager.USB_CHANNEL_ID, productName) {
|
||||
UsbCcidReaderFragment()
|
||||
})
|
||||
}
|
||||
viewPager.visibility = View.VISIBLE
|
||||
|
||||
if (newPages.size > 1) {
|
||||
tabs.visibility = View.VISIBLE
|
||||
} else if (newPages.isEmpty()) {
|
||||
newPages.add(
|
||||
Page(
|
||||
-1,
|
||||
""
|
||||
) { appContainer.uiComponentFactory.createNoEuiccPlaceholderFragment() })
|
||||
newPages.add(Page(-1, "") {
|
||||
appContainer.uiComponentFactory.createNoEuiccPlaceholderFragment()
|
||||
})
|
||||
}
|
||||
|
||||
newPages.sortBy { it.logicalSlotId }
|
||||
|
|
|
@ -38,14 +38,13 @@ class ProfileDeleteFragment : DialogFragment(), EuiccChannelFragmentMarker {
|
|||
get() = editText.text.toString() == requireArguments().getString("name")!!
|
||||
private var deleting = false
|
||||
|
||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||
return AlertDialog.Builder(requireContext(), R.style.AlertDialogTheme).apply {
|
||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog =
|
||||
AlertDialog.Builder(requireContext(), R.style.AlertDialogTheme).apply {
|
||||
setMessage(getString(R.string.profile_delete_confirm, requireArguments().getString("name")))
|
||||
setView(editText)
|
||||
setPositiveButton(android.R.string.ok, null) // Set listener to null to prevent auto closing
|
||||
setNegativeButton(android.R.string.cancel, null)
|
||||
}.create()
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
|
|
|
@ -1,298 +0,0 @@
|
|||
package im.angry.openeuicc.ui
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Dialog
|
||||
import android.content.DialogInterface
|
||||
import android.graphics.BitmapFactory
|
||||
import android.os.Bundle
|
||||
import android.text.Editable
|
||||
import android.util.Log
|
||||
import android.view.*
|
||||
import android.widget.ProgressBar
|
||||
import android.widget.TextView
|
||||
import android.widget.Toast
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.appcompat.widget.Toolbar
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.google.android.material.textfield.TextInputLayout
|
||||
import com.journeyapps.barcodescanner.ScanContract
|
||||
import com.journeyapps.barcodescanner.ScanOptions
|
||||
import im.angry.openeuicc.common.R
|
||||
import im.angry.openeuicc.service.EuiccChannelManagerService
|
||||
import im.angry.openeuicc.service.EuiccChannelManagerService.Companion.waitDone
|
||||
import im.angry.openeuicc.util.*
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
class ProfileDownloadFragment : BaseMaterialDialogFragment(),
|
||||
Toolbar.OnMenuItemClickListener, EuiccChannelFragmentMarker {
|
||||
companion object {
|
||||
const val TAG = "ProfileDownloadFragment"
|
||||
|
||||
const val LOW_NVRAM_THRESHOLD = 30 * 1024 // < 30 KiB, the alert may fail
|
||||
|
||||
fun newInstance(slotId: Int, portId: Int, finishWhenDone: Boolean = false): ProfileDownloadFragment =
|
||||
newInstanceEuicc(ProfileDownloadFragment::class.java, slotId, portId) {
|
||||
putBoolean("finishWhenDone", finishWhenDone)
|
||||
}
|
||||
}
|
||||
|
||||
private lateinit var toolbar: Toolbar
|
||||
private lateinit var profileDownloadServer: TextInputLayout
|
||||
private lateinit var profileDownloadCode: TextInputLayout
|
||||
private lateinit var profileDownloadConfirmationCode: TextInputLayout
|
||||
private lateinit var profileDownloadIMEI: TextInputLayout
|
||||
private lateinit var profileDownloadFreeSpace: TextView
|
||||
private lateinit var progress: ProgressBar
|
||||
|
||||
private var freeNvram: Int = -1
|
||||
|
||||
private var downloading = false
|
||||
|
||||
private val finishWhenDone by lazy {
|
||||
requireArguments().getBoolean("finishWhenDone", false)
|
||||
}
|
||||
|
||||
private val barcodeScannerLauncher = registerForActivityResult(ScanContract()) { result ->
|
||||
result.contents?.let { content ->
|
||||
onScanResult(content)
|
||||
}
|
||||
}
|
||||
|
||||
private val gallerySelectorLauncher = registerForActivityResult(ActivityResultContracts.GetContent()) { result ->
|
||||
if (result == null) return@registerForActivityResult
|
||||
|
||||
lifecycleScope.launch(Dispatchers.IO) {
|
||||
runCatching {
|
||||
requireContext().contentResolver.openInputStream(result)?.let { input ->
|
||||
val bmp = BitmapFactory.decodeStream(input)
|
||||
input.close()
|
||||
|
||||
decodeQrFromBitmap(bmp)?.let {
|
||||
withContext(Dispatchers.Main) {
|
||||
onScanResult(it)
|
||||
}
|
||||
}
|
||||
|
||||
bmp.recycle()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun onScanResult(result: String) {
|
||||
val components = result.split("$")
|
||||
if (components.size < 3 || components[0] != "LPA:1") return
|
||||
profileDownloadServer.editText?.setText(components[1])
|
||||
profileDownloadCode.editText?.setText(components[2])
|
||||
}
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View {
|
||||
val view = inflater.inflate(R.layout.fragment_profile_download, container, false)
|
||||
|
||||
toolbar = view.requireViewById(R.id.toolbar)
|
||||
profileDownloadServer = view.requireViewById(R.id.profile_download_server)
|
||||
profileDownloadCode = view.requireViewById(R.id.profile_download_code)
|
||||
profileDownloadConfirmationCode = view.requireViewById(R.id.profile_download_confirmation_code)
|
||||
profileDownloadIMEI = view.requireViewById(R.id.profile_download_imei)
|
||||
profileDownloadFreeSpace = view.requireViewById(R.id.profile_download_free_space)
|
||||
progress = view.requireViewById(R.id.progress)
|
||||
|
||||
toolbar.inflateMenu(R.menu.fragment_profile_download)
|
||||
|
||||
return view
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
toolbar.apply {
|
||||
setTitle(R.string.profile_download)
|
||||
setNavigationOnClickListener {
|
||||
if (!downloading) {
|
||||
dismiss()
|
||||
}
|
||||
}
|
||||
setOnMenuItemClickListener(this@ProfileDownloadFragment)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onMenuItemClick(item: MenuItem): Boolean = downloading ||
|
||||
when (item.itemId) {
|
||||
R.id.scan -> {
|
||||
barcodeScannerLauncher.launch(ScanOptions().apply {
|
||||
setDesiredBarcodeFormats(ScanOptions.QR_CODE)
|
||||
setOrientationLocked(false)
|
||||
})
|
||||
true
|
||||
}
|
||||
R.id.scan_from_gallery -> {
|
||||
gallerySelectorLauncher.launch("image/*")
|
||||
true
|
||||
}
|
||||
R.id.ok -> {
|
||||
if (freeNvram > LOW_NVRAM_THRESHOLD) {
|
||||
startDownloadProfile()
|
||||
} else {
|
||||
AlertDialog.Builder(requireContext()).apply {
|
||||
setTitle(R.string.profile_download_low_nvram_title)
|
||||
setMessage(R.string.profile_download_low_nvram_message)
|
||||
setIcon(android.R.drawable.ic_dialog_alert)
|
||||
setCancelable(true)
|
||||
setPositiveButton(android.R.string.ok) { _, _ ->
|
||||
startDownloadProfile()
|
||||
}
|
||||
setNegativeButton(android.R.string.cancel, null)
|
||||
show()
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
else -> false
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
setWidthPercent(95)
|
||||
}
|
||||
|
||||
@SuppressLint("MissingPermission")
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
|
||||
lifecycleScope.launch(Dispatchers.IO) {
|
||||
ensureEuiccChannelManager()
|
||||
if (euiccChannelManagerService.isForegroundTaskRunning) {
|
||||
withContext(Dispatchers.Main) {
|
||||
dismiss()
|
||||
}
|
||||
return@launch
|
||||
}
|
||||
|
||||
withEuiccChannel { channel ->
|
||||
val imei = try {
|
||||
telephonyManager.getImei(channel.logicalSlotId) ?: ""
|
||||
} catch (e: Exception) {
|
||||
""
|
||||
}
|
||||
|
||||
// Fetch remaining NVRAM
|
||||
val str = channel.lpa.euiccInfo2?.freeNvram?.also {
|
||||
freeNvram = it
|
||||
}?.let { formatFreeSpace(it) }
|
||||
|
||||
withContext(Dispatchers.Main) {
|
||||
profileDownloadFreeSpace.text = getString(
|
||||
R.string.profile_download_free_space,
|
||||
str ?: getText(R.string.unknown)
|
||||
)
|
||||
profileDownloadIMEI.editText!!.text =
|
||||
Editable.Factory.getInstance().newEditable(imei)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||
return super.onCreateDialog(savedInstanceState).also {
|
||||
it.setCanceledOnTouchOutside(false)
|
||||
}
|
||||
}
|
||||
|
||||
private fun startDownloadProfile() {
|
||||
val server = profileDownloadServer.editText!!.let {
|
||||
it.text.toString().trim().apply {
|
||||
if (isEmpty()) {
|
||||
it.requestFocus()
|
||||
return@startDownloadProfile
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val code = profileDownloadCode.editText!!.text.toString().trim()
|
||||
.ifBlank { null }
|
||||
val confirmationCode = profileDownloadConfirmationCode.editText!!.text.toString().trim()
|
||||
.ifBlank { null }
|
||||
val imei = profileDownloadIMEI.editText!!.text.toString().trim()
|
||||
.ifBlank { null }
|
||||
|
||||
downloading = true
|
||||
|
||||
profileDownloadServer.editText!!.isEnabled = false
|
||||
profileDownloadCode.editText!!.isEnabled = false
|
||||
profileDownloadConfirmationCode.editText!!.isEnabled = false
|
||||
profileDownloadIMEI.editText!!.isEnabled = false
|
||||
|
||||
progress.isIndeterminate = true
|
||||
progress.visibility = View.VISIBLE
|
||||
|
||||
lifecycleScope.launch {
|
||||
ensureEuiccChannelManager()
|
||||
euiccChannelManagerService.waitForForegroundTask()
|
||||
val err = doDownloadProfile(server, code, confirmationCode, imei)
|
||||
|
||||
if (err != null) {
|
||||
Log.d(TAG, "Error downloading profile")
|
||||
Log.d(TAG, Log.getStackTraceString(err))
|
||||
|
||||
Toast.makeText(requireContext(), R.string.profile_download_failed, Toast.LENGTH_LONG).show()
|
||||
}
|
||||
|
||||
if (parentFragment is EuiccProfilesChangedListener) {
|
||||
(parentFragment as EuiccProfilesChangedListener).onEuiccProfilesChanged()
|
||||
}
|
||||
|
||||
try {
|
||||
dismiss()
|
||||
} catch (e: IllegalStateException) {
|
||||
// Ignored
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun doDownloadProfile(
|
||||
server: String,
|
||||
code: String?,
|
||||
confirmationCode: String?,
|
||||
imei: String?
|
||||
) = withContext(Dispatchers.Main) {
|
||||
// The service is responsible for launching the actual blocking part on the IO context
|
||||
// On our side, we need the Main context because of the UI updates
|
||||
euiccChannelManagerService.launchProfileDownloadTask(
|
||||
slotId,
|
||||
portId,
|
||||
server,
|
||||
code,
|
||||
confirmationCode,
|
||||
imei
|
||||
).onEach {
|
||||
if (it is EuiccChannelManagerService.ForegroundTaskState.InProgress) {
|
||||
progress.progress = it.progress
|
||||
progress.isIndeterminate = it.progress == 0
|
||||
} else {
|
||||
progress.progress = 100
|
||||
progress.isIndeterminate = false
|
||||
}
|
||||
}.waitDone()
|
||||
}
|
||||
|
||||
override fun onDismiss(dialog: DialogInterface) {
|
||||
super.onDismiss(dialog)
|
||||
if (finishWhenDone) {
|
||||
activity?.finish()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCancel(dialog: DialogInterface) {
|
||||
super.onCancel(dialog)
|
||||
if (finishWhenDone) {
|
||||
activity?.finish()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -4,10 +4,14 @@ import android.os.Bundle
|
|||
import android.view.MenuItem
|
||||
import androidx.activity.enableEdgeToEdge
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import im.angry.openeuicc.OpenEuiccApplication
|
||||
import im.angry.openeuicc.common.R
|
||||
import im.angry.openeuicc.util.*
|
||||
|
||||
class SettingsActivity: AppCompatActivity() {
|
||||
private val appContainer
|
||||
get() = (application as OpenEuiccApplication).appContainer
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
enableEdgeToEdge()
|
||||
super.onCreate(savedInstanceState)
|
||||
|
@ -15,8 +19,9 @@ class SettingsActivity: AppCompatActivity() {
|
|||
setSupportActionBar(requireViewById(R.id.toolbar))
|
||||
setupToolbarInsets()
|
||||
supportActionBar!!.setDisplayHomeAsUpEnabled(true)
|
||||
val settingsFragment = appContainer.uiComponentFactory.createSettingsFragment()
|
||||
supportFragmentManager.beginTransaction()
|
||||
.replace(R.id.settings_container, SettingsFragment())
|
||||
.replace(R.id.settings_container, settingsFragment)
|
||||
.commit()
|
||||
}
|
||||
|
||||
|
|
|
@ -20,7 +20,7 @@ import kotlinx.coroutines.flow.onEach
|
|||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
|
||||
class SettingsFragment: PreferenceFragmentCompat() {
|
||||
open class SettingsFragment: PreferenceFragmentCompat() {
|
||||
private lateinit var developerPref: PreferenceCategory
|
||||
|
||||
// Hidden developer options switch
|
||||
|
@ -35,9 +35,9 @@ class SettingsFragment: PreferenceFragmentCompat() {
|
|||
|
||||
// Show / hide developer preference based on whether it is enabled
|
||||
lifecycleScope.launch {
|
||||
preferenceRepository.developerOptionsEnabledFlow.onEach {
|
||||
developerPref.isVisible = it
|
||||
}.collect()
|
||||
preferenceRepository.developerOptionsEnabledFlow
|
||||
.onEach { developerPref.isVisible = it }
|
||||
.collect()
|
||||
}
|
||||
|
||||
findPreference<Preference>("pref_info_app_version")?.apply {
|
||||
|
@ -74,9 +74,6 @@ class SettingsFragment: PreferenceFragmentCompat() {
|
|||
findPreference<CheckBoxPreference>("pref_advanced_verbose_logging")
|
||||
?.bindBooleanFlow(preferenceRepository.verboseLoggingFlow, PreferenceKeys.VERBOSE_LOGGING)
|
||||
|
||||
findPreference<CheckBoxPreference>("pref_developer_experimental_download_wizard")
|
||||
?.bindBooleanFlow(preferenceRepository.experimentalDownloadWizardFlow, PreferenceKeys.EXPERIMENTAL_DOWNLOAD_WIZARD)
|
||||
|
||||
findPreference<CheckBoxPreference>("pref_developer_unfiltered_profile_list")
|
||||
?.bindBooleanFlow(preferenceRepository.unfilteredProfileListFlow, PreferenceKeys.UNFILTERED_PROFILE_LIST)
|
||||
|
||||
|
@ -139,4 +136,22 @@ class SettingsFragment: PreferenceFragmentCompat() {
|
|||
true
|
||||
}
|
||||
}
|
||||
|
||||
protected fun mergePreferenceOverlay(overlayKey: String, targetKey: String) {
|
||||
val overlayCat = findPreference<PreferenceCategory>(overlayKey)!!
|
||||
val targetCat = findPreference<PreferenceCategory>(targetKey)!!
|
||||
|
||||
val prefs = buildList {
|
||||
for (i in 0..<overlayCat.preferenceCount) {
|
||||
add(overlayCat.getPreference(i))
|
||||
}
|
||||
}
|
||||
|
||||
prefs.forEach {
|
||||
overlayCat.removePreference(it)
|
||||
targetCat.addPreference(it)
|
||||
}
|
||||
|
||||
overlayCat.parent?.removePreference(overlayCat)
|
||||
}
|
||||
}
|
|
@ -120,7 +120,7 @@ class UsbCcidReaderFragment : Fragment(), OpenEuiccContextMarker {
|
|||
try {
|
||||
requireContext().unregisterReceiver(usbPermissionReceiver)
|
||||
} catch (_: Exception) {
|
||||
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -129,7 +129,7 @@ class UsbCcidReaderFragment : Fragment(), OpenEuiccContextMarker {
|
|||
try {
|
||||
requireContext().unregisterReceiver(usbPermissionReceiver)
|
||||
} catch (_: Exception) {
|
||||
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -155,8 +155,8 @@ class UsbCcidReaderFragment : Fragment(), OpenEuiccContextMarker {
|
|||
replace(
|
||||
R.id.child_container,
|
||||
appContainer.uiComponentFactory.createEuiccManagementFragment(
|
||||
EuiccChannelManager.USB_CHANNEL_ID,
|
||||
0
|
||||
slotId = EuiccChannelManager.USB_CHANNEL_ID,
|
||||
portId = 0
|
||||
)
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,12 +1,16 @@
|
|||
package im.angry.openeuicc.ui.wizard
|
||||
|
||||
import android.icu.text.SimpleDateFormat
|
||||
import android.os.Bundle
|
||||
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() {
|
||||
override val hasNext: Boolean
|
||||
|
@ -16,6 +20,16 @@ class DownloadWizardDiagnosticsFragment : DownloadWizardActivity.DownloadWizardS
|
|||
|
||||
private lateinit var diagnosticTextView: TextView
|
||||
|
||||
private val saveDiagnostics =
|
||||
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
|
||||
|
||||
override fun createPrevFragment(): DownloadWizardActivity.DownloadWizardStepFragment? = null
|
||||
|
@ -26,7 +40,15 @@ class DownloadWizardDiagnosticsFragment : DownloadWizardActivity.DownloadWizardS
|
|||
savedInstanceState: Bundle?
|
||||
): View? {
|
||||
val view = inflater.inflate(R.layout.fragment_download_diagnostics, container, false)
|
||||
diagnosticTextView = view.requireViewById<TextView>(R.id.download_wizard_diagnostics_text)
|
||||
view.requireViewById<View>(R.id.download_wizard_diagnostics_save).setOnClickListener {
|
||||
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
|
||||
}
|
||||
|
||||
|
@ -44,6 +66,14 @@ class DownloadWizardDiagnosticsFragment : DownloadWizardActivity.DownloadWizardS
|
|||
private fun buildDiagnosticsText(): String? = state.downloadError?.let { err ->
|
||||
val ret = StringBuilder()
|
||||
|
||||
ret.appendLine(
|
||||
getString(
|
||||
R.string.download_wizard_diagnostics_error_code,
|
||||
err.lpaErrorReason
|
||||
)
|
||||
)
|
||||
ret.appendLine()
|
||||
|
||||
err.lastHttpResponse?.let { resp ->
|
||||
if (resp.rcode != 200) {
|
||||
// Only show the status if it's not 200
|
||||
|
|
|
@ -113,18 +113,19 @@ class DownloadWizardProgressFragment : DownloadWizardActivity.DownloadWizardStep
|
|||
is EuiccChannelManagerService.ForegroundTaskState.Done -> {
|
||||
hideProgressBar()
|
||||
|
||||
// Change the state of the last InProgress item to Error
|
||||
state.downloadError =
|
||||
it.error as? LocalProfileAssistant.ProfileDownloadException
|
||||
|
||||
// Change the state of the last InProgress item to success (or error)
|
||||
progressItems.forEachIndexed { index, progressItem ->
|
||||
if (progressItem.state == ProgressState.InProgress) {
|
||||
progressItem.state = ProgressState.Error
|
||||
progressItem.state =
|
||||
if (state.downloadError == null) ProgressState.Done else ProgressState.Error
|
||||
}
|
||||
|
||||
adapter.notifyItemChanged(index)
|
||||
}
|
||||
|
||||
state.downloadError =
|
||||
it.error as? LocalProfileAssistant.ProfileDownloadException
|
||||
|
||||
isDone = true
|
||||
refreshButtons()
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ import android.view.View
|
|||
import android.view.ViewGroup
|
||||
import android.widget.CheckBox
|
||||
import android.widget.TextView
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.recyclerview.widget.DividerItemDecoration
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
|
@ -20,6 +21,11 @@ import kotlinx.coroutines.launch
|
|||
import net.typeblog.lpac_jni.LocalProfileInfo
|
||||
|
||||
class DownloadWizardSlotSelectFragment : DownloadWizardActivity.DownloadWizardStepFragment() {
|
||||
companion object {
|
||||
const val LOW_NVRAM_THRESHOLD =
|
||||
30 * 1024 // < 30 KiB, alert about potential download failure
|
||||
}
|
||||
|
||||
private data class SlotInfo(
|
||||
val logicalSlotId: Int,
|
||||
val isRemovable: Boolean,
|
||||
|
@ -45,6 +51,21 @@ class DownloadWizardSlotSelectFragment : DownloadWizardActivity.DownloadWizardSt
|
|||
|
||||
override fun createPrevFragment(): DownloadWizardActivity.DownloadWizardStepFragment? = null
|
||||
|
||||
override fun beforeNext() {
|
||||
super.beforeNext()
|
||||
|
||||
if (adapter.selected.freeSpace < LOW_NVRAM_THRESHOLD) {
|
||||
AlertDialog.Builder(requireContext()).apply {
|
||||
setTitle(R.string.profile_download_low_nvram_title)
|
||||
setMessage(R.string.profile_download_low_nvram_message)
|
||||
setCancelable(true)
|
||||
setPositiveButton(android.R.string.ok, null)
|
||||
setNegativeButton(android.R.string.cancel, null)
|
||||
show()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
|
@ -165,6 +186,9 @@ class DownloadWizardSlotSelectFragment : DownloadWizardActivity.DownloadWizardSt
|
|||
var slots: List<SlotInfo> = listOf()
|
||||
var currentSelectedIdx = -1
|
||||
|
||||
val selected: SlotInfo
|
||||
get() = slots[currentSelectedIdx]
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SlotItemHolder {
|
||||
val root = LayoutInflater.from(parent.context).inflate(R.layout.download_slot_item, parent, false)
|
||||
return SlotItemHolder(root)
|
||||
|
|
|
@ -31,7 +31,6 @@ object PreferenceKeys {
|
|||
|
||||
// ---- Developer Options ----
|
||||
val DEVELOPER_OPTIONS_ENABLED = booleanPreferencesKey("developer_options_enabled")
|
||||
val EXPERIMENTAL_DOWNLOAD_WIZARD = booleanPreferencesKey("experimental_download_wizard")
|
||||
val UNFILTERED_PROFILE_LIST = booleanPreferencesKey("unfiltered_profile_list")
|
||||
val IGNORE_TLS_CERTIFICATE = booleanPreferencesKey("ignore_tls_certificate")
|
||||
}
|
||||
|
@ -49,7 +48,6 @@ class PreferenceRepository(private val context: Context) {
|
|||
|
||||
// ---- Developer Options ----
|
||||
val developerOptionsEnabledFlow = bindFlow(PreferenceKeys.DEVELOPER_OPTIONS_ENABLED, false)
|
||||
val experimentalDownloadWizardFlow = bindFlow(PreferenceKeys.EXPERIMENTAL_DOWNLOAD_WIZARD, false)
|
||||
val unfilteredProfileListFlow = bindFlow(PreferenceKeys.UNFILTERED_PROFILE_LIST, false)
|
||||
val ignoreTLSCertificateFlow = bindFlow(PreferenceKeys.IGNORE_TLS_CERTIFICATE, false)
|
||||
|
||||
|
|
|
@ -16,10 +16,10 @@
|
|||
android:layout_height="wrap_content"
|
||||
android:gravity="center_horizontal"
|
||||
android:textSize="20sp"
|
||||
android:layout_marginTop="20sp"
|
||||
android:layout_marginBottom="20sp"
|
||||
android:layout_marginStart="60sp"
|
||||
android:layout_marginEnd="60sp"
|
||||
android:layout_marginTop="20dp"
|
||||
android:layout_marginBottom="20dp"
|
||||
android:layout_marginStart="60dp"
|
||||
android:layout_marginEnd="60dp"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constrainedWidth="true"
|
||||
|
|
|
@ -17,15 +17,26 @@
|
|||
android:layout_height="wrap_content"
|
||||
android:gravity="center_horizontal"
|
||||
android:textSize="20sp"
|
||||
android:layout_marginTop="20sp"
|
||||
android:layout_marginBottom="20sp"
|
||||
android:layout_marginStart="60sp"
|
||||
android:layout_marginEnd="60sp"
|
||||
android:layout_marginTop="20dp"
|
||||
android:layout_marginBottom="20dp"
|
||||
android:layout_marginStart="60dp"
|
||||
android:layout_marginEnd="60dp"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constrainedWidth="true"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/download_wizard_diagnostics_save"
|
||||
android:src="@drawable/ic_save_as_black"
|
||||
android:layout_margin="20dp"
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:contentDescription="@string/download_wizard_diagnostics_save"
|
||||
app:tint="?attr/colorAccent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/download_wizard_diagnostics_text"
|
||||
android:layout_width="match_parent"
|
||||
|
|
|
@ -11,10 +11,10 @@
|
|||
android:layout_height="wrap_content"
|
||||
android:gravity="center_horizontal"
|
||||
android:textSize="20sp"
|
||||
android:layout_marginTop="20sp"
|
||||
android:layout_marginBottom="20sp"
|
||||
android:layout_marginStart="60sp"
|
||||
android:layout_marginEnd="60sp"
|
||||
android:layout_marginTop="20dp"
|
||||
android:layout_marginBottom="20dp"
|
||||
android:layout_marginStart="60dp"
|
||||
android:layout_marginEnd="60dp"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constrainedWidth="true"
|
||||
|
|
|
@ -11,10 +11,10 @@
|
|||
android:layout_height="wrap_content"
|
||||
android:gravity="center_horizontal"
|
||||
android:textSize="20sp"
|
||||
android:layout_marginTop="20sp"
|
||||
android:layout_marginBottom="20sp"
|
||||
android:layout_marginStart="60sp"
|
||||
android:layout_marginEnd="60sp"
|
||||
android:layout_marginTop="20dp"
|
||||
android:layout_marginBottom="20dp"
|
||||
android:layout_marginStart="60dp"
|
||||
android:layout_marginEnd="60dp"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constrainedWidth="true"
|
||||
|
|
|
@ -11,10 +11,10 @@
|
|||
android:layout_height="wrap_content"
|
||||
android:gravity="center_horizontal"
|
||||
android:textSize="20sp"
|
||||
android:layout_marginTop="20sp"
|
||||
android:layout_marginBottom="20sp"
|
||||
android:layout_marginStart="60sp"
|
||||
android:layout_marginEnd="60sp"
|
||||
android:layout_marginTop="20dp"
|
||||
android:layout_marginBottom="20dp"
|
||||
android:layout_marginStart="60dp"
|
||||
android:layout_marginEnd="60dp"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constrainedWidth="true"
|
||||
|
|
|
@ -1,126 +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"
|
||||
android:background="?attr/colorSurface">
|
||||
|
||||
<com.google.android.material.appbar.MaterialToolbar
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintWidth_percent="1"
|
||||
app:navigationIcon="?homeAsUpIndicator" />
|
||||
|
||||
<View
|
||||
android:id="@+id/guideline"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:orientation="vertical"
|
||||
android:visibility="invisible"
|
||||
app:layout_constraintBottom_toBottomOf="@id/toolbar"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent" />
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/progress"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintTop_toBottomOf="@id/toolbar"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
app:layout_constraintBottom_toTopOf="@id/guideline"
|
||||
style="@style/Widget.AppCompat.ProgressBar.Horizontal" />
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/profile_download_server"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="15dp"
|
||||
android:hint="@string/profile_download_server"
|
||||
app:layout_constraintTop_toBottomOf="@id/toolbar"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
app:layout_constraintWidth_percent=".8">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" />
|
||||
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/profile_download_code"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginVertical="15dp"
|
||||
android:hint="@string/profile_download_code"
|
||||
app:layout_constraintTop_toBottomOf="@id/profile_download_server"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
app:layout_constraintWidth_percent=".8"
|
||||
app:passwordToggleEnabled="true">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:inputType="textPassword" />
|
||||
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/profile_download_confirmation_code"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginVertical="15dp"
|
||||
android:hint="@string/profile_download_confirmation_code"
|
||||
app:layout_constraintTop_toBottomOf="@id/profile_download_code"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
app:layout_constraintWidth_percent=".8"
|
||||
app:passwordToggleEnabled="true">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:inputType="textPassword" />
|
||||
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/profile_download_imei"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="15dp"
|
||||
android:layout_marginBottom="6dp"
|
||||
android:hint="@string/profile_download_imei"
|
||||
app:layout_constraintTop_toBottomOf="@id/profile_download_confirmation_code"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
app:layout_constraintBottom_toTopOf="@id/profile_download_free_space"
|
||||
app:layout_constraintWidth_percent=".8"
|
||||
app:passwordToggleEnabled="true">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:inputType="textPassword" />
|
||||
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/profile_download_free_space"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:textSize="11sp"
|
||||
android:layout_marginBottom="4dp"
|
||||
app:layout_constraintTop_toBottomOf="@id/profile_download_imei"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
@ -1,21 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
<item
|
||||
android:id="@+id/scan"
|
||||
android:icon="@drawable/ic_scan_black"
|
||||
android:title="@string/profile_download_scan"
|
||||
app:showAsAction="ifRoom"/>
|
||||
|
||||
<item
|
||||
android:id="@+id/scan_from_gallery"
|
||||
android:icon="@drawable/ic_gallery_black"
|
||||
android:title="@string/profile_download_scan_from_gallery"
|
||||
app:showAsAction="ifRoom" />
|
||||
|
||||
<item
|
||||
android:id="@+id/ok"
|
||||
android:icon="@drawable/ic_check_black"
|
||||
android:title="@string/profile_download_ok"
|
||||
app:showAsAction="always"/>
|
||||
</menu>
|
|
@ -37,11 +37,6 @@
|
|||
<string name="profile_download_code">アクティベーションコード</string>
|
||||
<string name="profile_download_confirmation_code">確認コード (オプション)</string>
|
||||
<string name="profile_download_imei">IMEI (オプション)</string>
|
||||
<string name="profile_download_free_space">残りの容量: %s</string>
|
||||
<string name="profile_download_scan">QR コードをスキャン</string>
|
||||
<string name="profile_download_scan_from_gallery">ギャラリーから QR コードをスキャン</string>
|
||||
<string name="profile_download_ok">ダウンロード</string>
|
||||
<string name="profile_download_failed">eSIM のダウンロードに失敗しました。アクティベーションまたは QR コードを確認してください。</string>
|
||||
<string name="profile_download_low_nvram_title">ダウンロードに失敗する可能性があります</string>
|
||||
<string name="profile_download_low_nvram_message">残り容量が少ないため、ダウンロードに失敗する可能性があります。</string>
|
||||
<string name="download_wizard">ダウンロードウィザード</string>
|
||||
|
@ -53,7 +48,6 @@
|
|||
<string name="download_wizard_slot_type_removable">リムーバブル</string>
|
||||
<string name="download_wizard_slot_type_internal">内部</string>
|
||||
<string name="download_wizard_slot_type_internal_port">内部 - ポート: %d</string>
|
||||
<string name="download_wizard_slot_eid">eID:</string>
|
||||
<string name="download_wizard_slot_active_profile">有効なプロファイル:</string>
|
||||
<string name="download_wizard_slot_free_space">空き容量:</string>
|
||||
<string name="download_wizard_method_select">eSIM プロファイルをどの方法でダウンロードしますか?</string>
|
||||
|
@ -68,6 +62,7 @@
|
|||
<string name="download_wizard_progress_step_downloading">eSIM プロファイルをダウンロード中です</string>
|
||||
<string name="download_wizard_progress_step_finalizing">eSIM プロファイルをストレージに読み込み中です</string>
|
||||
<string name="download_wizard_diagnostics">エラー診断</string>
|
||||
<string name="download_wizard_diagnostics_error_code">エラーコード: %s</string>
|
||||
<string name="download_wizard_diagnostics_last_http_status">最終の HTTP ステータス (サーバー): %d</string>
|
||||
<string name="download_wizard_diagnostics_last_http_response">最終の HTTP レスポンス (サーバー):</string>
|
||||
<string name="download_wizard_diagnostics_last_http_exception">最終の HTTP 例外:</string>
|
||||
|
@ -75,6 +70,8 @@
|
|||
<string name="download_wizard_diagnostics_last_apdu_response_success">最終の APDU レスポンス (SIM) は成功しました</string>
|
||||
<string name="download_wizard_diagnostics_last_apdu_response_fail">最終の APDU レスポンス (SIM) は失敗しました</string>
|
||||
<string name="download_wizard_diagnostics_last_apdu_exception">最終の APDU 例外:</string>
|
||||
<string name="download_wizard_diagnostics_save">保存</string>
|
||||
<string name="download_wizard_diagnostics_file_template">%s のエラー診断</string>
|
||||
<string name="profile_rename_new_name">新しいニックネーム</string>
|
||||
<string name="profile_delete_confirm">%s のプロファイルを削除してもよろしいですか?この操作は元に戻せません。</string>
|
||||
<string name="profile_delete_confirm_input">削除を確認するには「%s」を入力してください</string>
|
||||
|
@ -92,11 +89,10 @@
|
|||
<string name="euicc_info_activity_title">eUICC 情報 (%s)</string>
|
||||
<string name="euicc_info_access_mode">アクセスモード</string>
|
||||
<string name="euicc_info_removable">リムーバブル</string>
|
||||
<string name="euicc_info_eid">EID</string>
|
||||
<string name="euicc_info_firmware_version">eUICC OS のバージョン</string>
|
||||
<string name="euicc_info_globalplatform_version">グローバルプラットフォームのバージョン</string>
|
||||
<string name="euicc_info_sas_accreditation_number">SAS 認定番号</string>
|
||||
<string name="euicc_info_pp_version">保護されたプロファイルのバージョン</string>
|
||||
<string name="euicc_info_pp_version">Protected Profileのバージョン</string>
|
||||
<string name="euicc_info_free_nvram">NVRAM の空き容量 (eSIM プロファイルストレージ)</string>
|
||||
<string name="euicc_info_gsma_prod">GSMA プロダクション証明書</string>
|
||||
<string name="euicc_info_gsma_test">GSMA テスト証明書</string>
|
||||
|
@ -125,8 +121,6 @@
|
|||
<string name="pref_advanced_logs">ログ</string>
|
||||
<string name="pref_advanced_logs_desc">アプリの最新デバッグログを表示します</string>
|
||||
<string name="pref_developer">開発者オプション</string>
|
||||
<string name="pref_developer_experimental_download_wizard">実験的なダウンロードウィザード</string>
|
||||
<string name="pref_developer_experimental_download_wizard_desc">実験的な新しいダウンロードウィザードを有効化します。まだ完全に機能していないことにご注意ください。</string>
|
||||
<string name="pref_developer_ignore_tls_certificate">SM-DP+ TLS 証明書を無視する</string>
|
||||
<string name="pref_developer_ignore_tls_certificate_desc">SM-DP+ TLS 証明書を無視して任意の RSP を許可します</string>
|
||||
<string name="pref_info">情報</string>
|
||||
|
|
137
app-common/src/main/res/values-zh-rCN/strings.xml
Normal file
137
app-common/src/main/res/values-zh-rCN/strings.xml
Normal file
|
@ -0,0 +1,137 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="no_euicc">在此设备上未检测到此应用程序可访问的可插拔 eUICC 卡。请插入兼容卡或 USB 读卡器。</string>
|
||||
<string name="no_profile">此 eSIM 上还没有配置文件</string>
|
||||
<string name="unknown">未知</string>
|
||||
<string name="help">帮助</string>
|
||||
<string name="reload">重新加载卡槽</string>
|
||||
<string name="channel_name_format">逻辑卡槽 %d</string>
|
||||
<string name="enabled">已启用</string>
|
||||
<string name="disabled">已禁用</string>
|
||||
<string name="provider">提供商:</string>
|
||||
<string name="profile_class">类型:</string>
|
||||
<string name="enable">启用</string>
|
||||
<string name="disable">禁用</string>
|
||||
<string name="delete">删除</string>
|
||||
<string name="rename">重命名</string>
|
||||
<string name="enable_disable_timeout">等待 eSIM 芯片切换配置文件时超时。这可能是您手机基带固件中的一个错误。请尝试切换飞行模式、重新启动应用程序或重新启动手机</string>
|
||||
<string name="switch_did_not_refresh">操作成功, 但是您手机的基带拒绝刷新。您可能需要切换飞行模式或重新启动,以便使用新的配置文件。</string>
|
||||
<string name="toast_profile_enable_failed">无法切换到新的 eSIM 配置文件。</string>
|
||||
<string name="toast_profile_name_too_long">昵称不能超过 64 个字符</string>
|
||||
<string name="toast_iccid_copied">已复制 ICCID 到剪贴板</string>
|
||||
<string name="slot_select">选择卡槽</string>
|
||||
<string name="slot_select_select">选择</string>
|
||||
<string name="usb_permission">授予 USB 权限</string>
|
||||
<string name="usb_permission_needed">需要获得访问 USB 智能卡读卡器的权限。</string>
|
||||
<string name="usb_failed">无法通过 USB 智能卡读卡器连接到 eSIM。</string>
|
||||
<string name="task_notification">长时间运行的后台任务</string>
|
||||
<string name="task_profile_download">正在下载 eSIM 配置文件</string>
|
||||
<string name="task_profile_download_failure">无法下载 eSIM 配置文件</string>
|
||||
<string name="task_profile_rename">正在重命名 eSIM 配置文件</string>
|
||||
<string name="task_profile_rename_failure">无法重命名 eSIM 配置文件</string>
|
||||
<string name="task_profile_delete">正在删除 eSIM 配置文件</string>
|
||||
<string name="task_profile_delete_failure">无法删除 eSIM 配置文件</string>
|
||||
<string name="task_profile_switch">正在切换 eSIM 配置文件</string>
|
||||
<string name="task_profile_switch_failure">无法切换 eSIM 配置文件</string>
|
||||
<string name="profile_download">添加新 eSIM</string>
|
||||
<string name="profile_download_server">服务器 (RSP / SM-DP+)</string>
|
||||
<string name="profile_download_code">激活码</string>
|
||||
<string name="profile_download_confirmation_code">确认码 (可选)</string>
|
||||
<string name="profile_download_imei">IMEI (可选)</string>
|
||||
<string name="profile_download_low_nvram_title">本次下载可能会失败</string>
|
||||
<string name="profile_download_low_nvram_message">当前芯片的剩余空间不足,可能导致配置下载失败。\n是否继续下载?</string>
|
||||
<string name="profile_rename_new_name">新昵称</string>
|
||||
<string name="profile_delete_confirm">您确定要删除 %s 吗?此操作是不可逆的。</string>
|
||||
<string name="profile_delete_confirm_input">请输入\'%s\'以确认删除</string>
|
||||
<string name="profile_notifications">通知列表</string>
|
||||
<string name="profile_notifications_detailed_format">通知列表 (%s)</string>
|
||||
<string name="profile_notifications_show">管理通知</string>
|
||||
<string name="profile_notifications_help">eSIM 配置文件可以在下载、删除、启用或禁用时向运营商发送通知。此处列出了要发送的这些通知的队列。\n\n在\"设置\"中,您可以指定是否自动发送每种类型的通知。请注意,即使通知已发送,也不会自动从记录中删除,除非队列空间不足。\n\n在这里,您可以手动发送或删除每个待处理的通知。</string>
|
||||
<string name="profile_notification_operation_download">已下载</string>
|
||||
<string name="profile_notification_operation_delete">已删除</string>
|
||||
<string name="profile_notification_operation_enable">已启用</string>
|
||||
<string name="profile_notification_operation_disable">已禁用</string>
|
||||
<string name="profile_notification_process">处理</string>
|
||||
<string name="profile_notification_delete">删除</string>
|
||||
<string name="logs_save">保存日志</string>
|
||||
<string name="logs_filename_template">%s 的日志</string>
|
||||
<string name="pref_settings">设置</string>
|
||||
<string name="pref_notifications">通知</string>
|
||||
<string name="pref_notifications_desc">操作 eSIM 配置文件会向运营商发送通知。根据需要在此处微调此行为。</string>
|
||||
<string name="pref_notifications_download">下载</string>
|
||||
<string name="pref_notifications_download_desc">发送 <i>下载</i> 配置文件的通知</string>
|
||||
<string name="pref_notifications_delete">删除</string>
|
||||
<string name="pref_notifications_delete_desc">发送 <i>删除</i> 配置文件的通知</string>
|
||||
<string name="pref_notifications_switch">切换</string>
|
||||
<string name="pref_notifications_switch_desc">发送 <i>切换</i> 配置文件的通知\n注意,这种类型的通知是不可靠的。</string>
|
||||
<string name="pref_advanced">高级</string>
|
||||
<string name="pref_advanced_disable_safeguard_removable_esim">允许 禁用/删除 已启用的配置文件</string>
|
||||
<string name="pref_advanced_disable_safeguard_removable_esim_desc">默认情况下,此应用程序会阻止您禁用可插拔 eSIM 中已启用的配置文件。\n因为这样做 <i>有时</i> 会使其无法访问。\n勾选此框以 <i>移除</i> 此保护措施。</string>
|
||||
<string name="pref_advanced_verbose_logging">记录详细日志</string>
|
||||
<string name="pref_advanced_verbose_logging_desc">详细日志中包含敏感信息,开启此功能后请仅与你信任的人共享你的日志。</string>
|
||||
<string name="pref_advanced_logs">日志</string>
|
||||
<string name="pref_advanced_logs_desc">查看应用程序的最新调试日志</string>
|
||||
<string name="pref_info">信息</string>
|
||||
<string name="pref_info_app_version">App 版本</string>
|
||||
<string name="pref_info_source_code">源码</string>
|
||||
<string name="profile_class_testing">测试</string>
|
||||
<string name="profile_class_provisioning">准备中</string>
|
||||
<string name="profile_class_operational">可用</string>
|
||||
<string name="download_wizard">下载向导</string>
|
||||
<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>
|
||||
<string name="download_wizard_slot_type_internal_port">内置, 端口 %d</string>
|
||||
<string name="download_wizard_slot_active_profile">当前配置文件:</string>
|
||||
<string name="download_wizard_slot_free_space">剩余空间:</string>
|
||||
<string name="download_wizard_method_select">您想要如何下载 eSIM 配置文件?</string>
|
||||
<string name="download_wizard_method_qr_code">用相机扫描二维码</string>
|
||||
<string name="download_wizard_method_gallery">从图库选择二维码</string>
|
||||
<string name="download_wizard_method_manual">手动输入</string>
|
||||
<string name="download_wizard_details">请输入或确认下载 eSIM 的详细信息:</string>
|
||||
<string name="download_wizard_progress">正在下载您的 eSIM...</string>
|
||||
<string name="download_wizard_progress_step_preparing">准备中</string>
|
||||
<string name="download_wizard_progress_step_connecting">正在连接服务器</string>
|
||||
<string name="download_wizard_progress_step_authenticating">正在向服务器认证您的设备</string>
|
||||
<string name="download_wizard_progress_step_downloading">正在下载 eSIM 配置文件</string>
|
||||
<string name="download_wizard_progress_step_finalizing">正在写入 eSIM 配置文件</string>
|
||||
<string name="download_wizard_diagnostics">错误诊断</string>
|
||||
<string name="download_wizard_diagnostics_error_code">错误代码: %s</string>
|
||||
<string name="download_wizard_diagnostics_last_http_status">上次 HTTP 状态码 (来自服务器): %d</string>
|
||||
<string name="download_wizard_diagnostics_last_http_response">上次 HTTP 应答 (来自服务器):</string>
|
||||
<string name="download_wizard_diagnostics_last_http_exception">上次 HTTP 错误:</string>
|
||||
<string name="download_wizard_diagnostics_last_apdu_response">上次 APDU 应答 (来自 SIM): %s</string>
|
||||
<string name="download_wizard_diagnostics_last_apdu_response_success">上次 APDU 应答 (来自 SIM) 是成功的</string>
|
||||
<string name="download_wizard_diagnostics_last_apdu_response_fail">上次 APDU 应答 (来自 SIM) 是失败的</string>
|
||||
<string name="download_wizard_diagnostics_last_apdu_exception">上次 APDU 错误:</string>
|
||||
<string name="download_wizard_diagnostics_save">保存</string>
|
||||
<string name="download_wizard_diagnostics_file_template">%s 的错误诊断</string>
|
||||
<string name="euicc_info">eUICC 详情</string>
|
||||
<string name="euicc_info_activity_title">eUICC 详情 (%s)</string>
|
||||
<string name="euicc_info_access_mode">访问方式</string>
|
||||
<string name="euicc_info_removable">可插拔</string>
|
||||
<string name="euicc_info_firmware_version">eUICC OS 版本</string>
|
||||
<string name="euicc_info_globalplatform_version">GlobalPlatform 版本</string>
|
||||
<string name="euicc_info_sas_accreditation_number">SAS 认证号码</string>
|
||||
<string name="euicc_info_pp_version">Protected Profile 版本</string>
|
||||
<string name="euicc_info_free_nvram">NVRAM 剩余空间 (eSIM 存储容量)</string>
|
||||
<string name="euicc_info_gsma_prod">GSMA 生产环境证书</string>
|
||||
<string name="euicc_info_gsma_test">GSMA 测试环境证书</string>
|
||||
<string name="supported">兼容</string>
|
||||
<string name="unsupported">不兼容</string>
|
||||
<string name="yes">是</string>
|
||||
<string name="no">否</string>
|
||||
<string name="developer_options_steps">还有 %d 步成为开发者</string>
|
||||
<string name="developer_options_enabled">你现在是开发者了!</string>
|
||||
<string name="pref_language">语言</string>
|
||||
<string name="pref_language_desc">选择 App 语言</string>
|
||||
<string name="pref_developer">开发者选项</string>
|
||||
<string name="pref_developer_unfiltered_profile_list">显示未经过滤的配置文件列表</string>
|
||||
<string name="pref_developer_unfiltered_profile_list_desc">在配置文件列表中包括非生产环境的配置文件</string>
|
||||
<string name="pref_developer_ignore_tls_certificate">无视 SM-DP+ 的 TLS 证书</string>
|
||||
<string name="pref_developer_ignore_tls_certificate_desc">允许 RSP 服务器使用任意证书</string>
|
||||
</resources>
|
|
@ -53,11 +53,6 @@
|
|||
<string name="profile_download_code">Activation Code</string>
|
||||
<string name="profile_download_confirmation_code">Confirmation Code (Optional)</string>
|
||||
<string name="profile_download_imei">IMEI (Optional)</string>
|
||||
<string name="profile_download_free_space">Space remaining: %s</string>
|
||||
<string name="profile_download_scan">Scan QR Code</string>
|
||||
<string name="profile_download_scan_from_gallery">Scan QR Code from Gallery</string>
|
||||
<string name="profile_download_ok">Download</string>
|
||||
<string name="profile_download_failed">Failed to download eSIM. Check your activation / QR code.</string>
|
||||
|
||||
<string name="profile_download_low_nvram_title">This download may fail</string>
|
||||
<string name="profile_download_low_nvram_message">This download may fail due to low remaining capacity.</string>
|
||||
|
@ -71,7 +66,7 @@
|
|||
<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_port">Internal, port %d</string>
|
||||
<string name="download_wizard_slot_eid">eID:</string>
|
||||
<string name="download_wizard_slot_eid" translatable="false">eID:</string>
|
||||
<string name="download_wizard_slot_active_profile">Active Profile:</string>
|
||||
<string name="download_wizard_slot_free_space">Free Space:</string>
|
||||
<string name="download_wizard_method_select">How would you like to download the eSIM profile?</string>
|
||||
|
@ -86,6 +81,7 @@
|
|||
<string name="download_wizard_progress_step_downloading">Downloading eSIM profile</string>
|
||||
<string name="download_wizard_progress_step_finalizing">Loading eSIM profile into storage</string>
|
||||
<string name="download_wizard_diagnostics">Error diagnostics</string>
|
||||
<string name="download_wizard_diagnostics_error_code">Error code: %s</string>
|
||||
<string name="download_wizard_diagnostics_last_http_status">Last HTTP status (from server): %d</string>
|
||||
<string name="download_wizard_diagnostics_last_http_response">Last HTTP response (from server):</string>
|
||||
<string name="download_wizard_diagnostics_last_http_exception">Last HTTP exception:</string>
|
||||
|
@ -93,6 +89,8 @@
|
|||
<string name="download_wizard_diagnostics_last_apdu_response_success">Last APDU response (from SIM) is successful</string>
|
||||
<string name="download_wizard_diagnostics_last_apdu_response_fail">Last APDU response (from SIM) is a failure</string>
|
||||
<string name="download_wizard_diagnostics_last_apdu_exception">Last APDU exception:</string>
|
||||
<string name="download_wizard_diagnostics_save">Save</string>
|
||||
<string name="download_wizard_diagnostics_file_template">Diagnostics at %s</string>
|
||||
|
||||
<string name="profile_rename_new_name">New nickname</string>
|
||||
|
||||
|
@ -116,7 +114,7 @@
|
|||
<string name="euicc_info_activity_title">eUICC Info (%s)</string>
|
||||
<string name="euicc_info_access_mode">Access Mode</string>
|
||||
<string name="euicc_info_removable">Removable</string>
|
||||
<string name="euicc_info_eid">EID</string>
|
||||
<string name="euicc_info_eid" translatable="false">EID</string>
|
||||
<string name="euicc_info_firmware_version">eUICC OS Version</string>
|
||||
<string name="euicc_info_globalplatform_version">GlobalPlatform Version</string>
|
||||
<string name="euicc_info_sas_accreditation_number">SAS Accreditation Number</string>
|
||||
|
@ -156,12 +154,10 @@
|
|||
<string name="pref_advanced_logs">Logs</string>
|
||||
<string name="pref_advanced_logs_desc">View recent debug logs of the application</string>
|
||||
<string name="pref_developer">Developer Options</string>
|
||||
<string name="pref_developer_experimental_download_wizard">Experimental Download Wizard</string>
|
||||
<string name="pref_developer_experimental_download_wizard_desc">Enable the experimental new download wizard. Note that it is not fully working yet.</string>
|
||||
<string name="pref_developer_unfiltered_profile_list">Show unfiltered profile list</string>
|
||||
<string name="pref_developer_unfiltered_profile_list_desc">Include non-production profiles in the list</string>
|
||||
<string name="pref_developer_ignore_tls_certificate">Ignore SM-DP+ TLS certificate</string>
|
||||
<string name="pref_developer_ignore_tls_certificate_desc">Ignore SM-DP+ TLS certificate, allow any RSP</string>
|
||||
<string name="pref_developer_ignore_tls_certificate_desc">Accept any TLS certificate used by the RSP server</string>
|
||||
<string name="pref_info">Info</string>
|
||||
<string name="pref_info_app_version">App Version</string>
|
||||
<string name="pref_info_source_code">Source Code</string>
|
||||
|
|
|
@ -2,4 +2,5 @@
|
|||
<locale-config xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<locale android:name="en-US" />
|
||||
<locale android:name="ja" />
|
||||
<locale android:name="zh-CN" />
|
||||
</locale-config>
|
|
@ -57,12 +57,6 @@
|
|||
app:title="@string/pref_developer"
|
||||
app:iconSpaceReserved="false">
|
||||
|
||||
<CheckBoxPreference
|
||||
app:key="pref_developer_experimental_download_wizard"
|
||||
app:iconSpaceReserved="false"
|
||||
app:title="@string/pref_developer_experimental_download_wizard"
|
||||
app:summary="@string/pref_developer_experimental_download_wizard_desc" />
|
||||
|
||||
<CheckBoxPreference
|
||||
app:iconSpaceReserved="false"
|
||||
app:key="pref_developer_unfiltered_profile_list"
|
||||
|
@ -78,6 +72,7 @@
|
|||
</PreferenceCategory>
|
||||
|
||||
<PreferenceCategory
|
||||
app:key="pref_info"
|
||||
app:title="@string/pref_info"
|
||||
app:iconSpaceReserved="false">
|
||||
<Preference
|
||||
|
|
|
@ -2,8 +2,10 @@ package im.angry.openeuicc.di
|
|||
|
||||
import androidx.fragment.app.Fragment
|
||||
import im.angry.openeuicc.ui.EuiccManagementFragment
|
||||
import im.angry.openeuicc.ui.SettingsFragment
|
||||
import im.angry.openeuicc.ui.UnprivilegedEuiccManagementFragment
|
||||
import im.angry.openeuicc.ui.UnprivilegedNoEuiccPlaceholderFragment
|
||||
import im.angry.openeuicc.ui.UnprivilegedSettingsFragment
|
||||
|
||||
class UnprivilegedUiComponentFactory : DefaultUiComponentFactory() {
|
||||
override fun createEuiccManagementFragment(slotId: Int, portId: Int): EuiccManagementFragment =
|
||||
|
@ -11,4 +13,7 @@ class UnprivilegedUiComponentFactory : DefaultUiComponentFactory() {
|
|||
|
||||
override fun createNoEuiccPlaceholderFragment(): Fragment =
|
||||
UnprivilegedNoEuiccPlaceholderFragment()
|
||||
|
||||
override fun createSettingsFragment(): Fragment =
|
||||
UnprivilegedSettingsFragment()
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
package im.angry.openeuicc.ui
|
||||
|
||||
import android.content.ClipData
|
||||
import android.content.ClipboardManager
|
||||
import android.content.pm.PackageManager
|
||||
import android.os.Bundle
|
||||
import android.widget.Toast
|
||||
import androidx.preference.Preference
|
||||
import im.angry.easyeuicc.R
|
||||
import im.angry.openeuicc.util.encodeHex
|
||||
import java.security.MessageDigest
|
||||
|
||||
class UnprivilegedSettingsFragment : SettingsFragment() {
|
||||
private val firstSigner by lazy {
|
||||
val packageInfo = requireContext().let {
|
||||
it.packageManager.getPackageInfo(
|
||||
it.packageName,
|
||||
PackageManager.GET_SIGNING_CERTIFICATES,
|
||||
)
|
||||
}
|
||||
packageInfo.signingInfo!!.apkContentsSigners.first().let {
|
||||
MessageDigest.getInstance("SHA-1")
|
||||
.apply { update(it.toByteArray()) }
|
||||
.digest()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||
super.onCreatePreferences(savedInstanceState, rootKey)
|
||||
addPreferencesFromResource(R.xml.pref_unprivileged_settings)
|
||||
mergePreferenceOverlay("pref_info_overlay", "pref_info")
|
||||
|
||||
findPreference<Preference>("pref_info_ara_m")?.apply {
|
||||
summary = firstSigner.encodeHex()
|
||||
setOnPreferenceClickListener {
|
||||
requireContext().getSystemService(ClipboardManager::class.java)!!
|
||||
.setPrimaryClip(ClipData.newPlainText("ara-m", summary))
|
||||
Toast.makeText(requireContext(), R.string.toast_ara_m_copied, Toast.LENGTH_SHORT)
|
||||
.show()
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -30,4 +30,5 @@
|
|||
<string name="compatibility_check_verdict_unknown_likely_fail">挿入された取り外し可能な eSIM がデバイス上で管理できるかどうかは判断できません。デバイスが OMAPI のサポートを宣言していないため、このデバイス上で取り外し可能な eSIM を管理することはサポートされていない可能性があります。\n%s</string>
|
||||
<string name="compatibility_check_verdict_unknown">挿入された取り外し可能な eSIM がデバイス上で管理できるかどうかを確認できません。\n%s</string>
|
||||
<string name="compatibility_check_verdict_fail_shared">ただし、eSIM プロファイルがすでに読み込まれている場合、有効化されたプロファイル自体は引き続き機能します。また、プロファイルが管理できない場合は、このデバイスで USB カードリーダーを介してプロファイルを管理できる可能性があります。</string>
|
||||
<string name="toast_ara_m_copied">ARA-M SHA-1 をクリップボードにコピーしました</string>
|
||||
</resources>
|
||||
|
|
32
app-unpriv/src/main/res/values-zh-rCN/strings.xml
Normal file
32
app-unpriv/src/main/res/values-zh-rCN/strings.xml
Normal file
|
@ -0,0 +1,32 @@
|
|||
<resources>
|
||||
<string name="compatibility_check">兼容性检查</string>
|
||||
<string name="open_sim_toolkit">打开 SIM 卡应用程序</string>
|
||||
<string name="compatibility_check_system_features">系统功能</string>
|
||||
<string name="compatibility_check_system_features_desc">您的设备是否具有管理可插拔 eUICC 卡所需的所有功能。例如,基本的电话功能和 OMAPI 支持。</string>
|
||||
<string name="compatibility_check_system_features_no_telephony">您的设备没有电话功能。</string>
|
||||
<string name="compatibility_check_system_features_no_omapi">您的设备/系统未声明支持 OMAPI。这可能是由于缺少硬件支持,或者可能仅仅是由于缺少标志。请参阅以下两项检查以确定 OMAPI 是否确实受支持。</string>
|
||||
<string name="compatibility_check_omapi_connectivity">OMAPI 连接</string>
|
||||
<string name="compatibility_check_omapi_connectivity_desc">您的设备是否允许通过 OMAPI 访问 SIM 卡上的安全元件?</string>
|
||||
<string name="compatibility_check_omapi_connectivity_fail">无法通过 OMAPI 检测到 SIM 卡的 Secure Element。如果您尚未在此设备中插入 SIM 卡,请尝试插入一张 SIM 卡并重试此检查。</string>
|
||||
<string name="compatibility_check_omapi_connectivity_partial_success_sim_number">已成功检测到可访问 Secure Element 的卡槽,但仅限于以下 SIM 卡槽:<b>SIM%s</b>。</string>
|
||||
<string name="compatibility_check_isdr_channel">ISD-R 通道访问</string>
|
||||
<string name="compatibility_check_isdr_channel_desc">您的设备是否支持通过 OMAPI 打开 eSIM 的 ISD-R (管理) 通道?</string>
|
||||
<string name="compatibility_check_isdr_channel_desc_unknown">无法确定是否支持通过 OMAPI 进行 ISD-R 访问。如果尚未插入,您可能需要插入 SIM 卡 (任何 SIM 卡都可以) 重试。</string>
|
||||
<string name="compatibility_check_isdr_channel_desc_partial_fail">OMAPI 只能在以下 SIM 插槽上访问 ISD-R:<b>SIM%s</b>。</string>
|
||||
<string name="compatibility_check_known_broken">不在已知的 BUG 名单中</string>
|
||||
<string name="compatibility_check_known_broken_desc">确保您的设备不存在与可插拔 eSIM 相关的错误。</string>
|
||||
<string name="compatibility_check_known_broken_fail">糟糕,您的设备在访问可插拔 eSIM 时存在错误。这并不表示完全无法使用,但我们不保证该应用在您设备上的行为。</string>
|
||||
<string name="compatibility_check_usb">USB 读卡器支持</string>
|
||||
<string name="compatibility_check_usb_desc">您的设备是否支持通过 USB 读卡器管理 eSIM?</string>
|
||||
<string name="compatibility_check_usb_ok">您可以通过此设备上的标准 USB CCID 读取器管理 eSIM (即使您在这里有任何其他检查项失败)。请插入读卡器,然后打开此应用程序以这种方式管理 eSIM。</string>
|
||||
<string name="compatibility_check_usb_fail">您的设备不支持 USB 读卡器。</string>
|
||||
<string name="compatibility_check_verdict">结论 (USB 读卡器以外)</string>
|
||||
<string name="compatibility_check_verdict_desc">根据之前的所有检查,您的设备与可插拔 eSIM 卡兼容的可能性有多大?</string>
|
||||
<string name="compatibility_check_verdict_ok">您可以使用和管理插入此设备的可插拔 eSIM 卡。</string>
|
||||
<string name="compatibility_check_verdict_known_broken">已知您的设备在访问可插拔 eSIM 卡时存在问题。\n%s</string>
|
||||
<string name="compatibility_check_verdict_unknown_likely_ok">我们无法确定是否可以在您的设备上管理可插拔 eSIM 卡。不过,您的设备确实声明支持 OMAPI,因此它工作的可能性略高。\n%s</string>
|
||||
<string name="compatibility_check_verdict_unknown_likely_fail">我们无法确定是否可以在您的设备上管理可插拔 eSIM 卡。由于您的设备未声明支持OMAPI,因此更有可能不支持在此设备上管理可插拔 eSIM。\n%s</string>
|
||||
<string name="compatibility_check_verdict_unknown">我们无法确定是否可以在您的设备上管理可插拔 eSIM 卡。\n%s</string>
|
||||
<string name="compatibility_check_verdict_fail_shared">然而,已经加载了eSIM配置文件的可插拔 eSIM 卡仍然可以工作; 即使无法在装置上直接管理可插拔 eSIM 卡中的配置文件,您仍然可以使用 USB 卡读卡器来管理配置文件。</string>
|
||||
<string name="toast_ara_m_copied">ARA-M SHA-1 已拷贝到剪贴板</string>
|
||||
</resources>
|
|
@ -4,6 +4,12 @@
|
|||
<string name="compatibility_check">Compatibility Check</string>
|
||||
<string name="open_sim_toolkit">Open SIM Toolkit</string>
|
||||
|
||||
<!-- Settings -->
|
||||
<string name="pref_developer_ara_m" translatable="false">ARA-M SHA-1</string>
|
||||
|
||||
<!-- Toast -->
|
||||
<string name="toast_ara_m_copied">ARA-M SHA-1 copied to clipboard</string>
|
||||
|
||||
<!-- Compatibility Check Descriptions -->
|
||||
<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>
|
||||
|
|
12
app-unpriv/src/main/res/xml/pref_unprivileged_settings.xml
Normal file
12
app-unpriv/src/main/res/xml/pref_unprivileged_settings.xml
Normal file
|
@ -0,0 +1,12 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<PreferenceScreen xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
<PreferenceCategory
|
||||
app:isPreferenceVisible="false"
|
||||
app:key="pref_info_overlay">
|
||||
<Preference
|
||||
app:enableCopying="true"
|
||||
app:iconSpaceReserved="false"
|
||||
app:key="pref_info_ara_m"
|
||||
app:title="@string/pref_developer_ara_m" />
|
||||
</PreferenceCategory>
|
||||
</PreferenceScreen>
|
|
@ -110,7 +110,7 @@ class OpenEuiccService : EuiccService(), OpenEuiccContextMarker {
|
|||
telephonyManager.simSlotMapping = mappings
|
||||
return
|
||||
} catch (_: Exception) {
|
||||
|
||||
// ignore
|
||||
}
|
||||
|
||||
// Sometimes hardware supports one ordering but not the reverse
|
||||
|
|
|
@ -8,6 +8,7 @@ import androidx.core.view.ViewCompat
|
|||
import androidx.core.view.WindowInsetsCompat
|
||||
import androidx.core.view.updatePadding
|
||||
import im.angry.openeuicc.R
|
||||
import im.angry.openeuicc.ui.wizard.DownloadWizardActivity
|
||||
|
||||
class LuiActivity : AppCompatActivity() {
|
||||
override fun onStart() {
|
||||
|
@ -25,10 +26,11 @@ class LuiActivity : AppCompatActivity() {
|
|||
}
|
||||
|
||||
requireViewById<View>(R.id.lui_skip).setOnClickListener { finish() }
|
||||
// TODO: Deactivate LuiActivity if there is no eSIM found.
|
||||
// TODO: Deactivate DownloadWizardActivity if there is no eSIM found.
|
||||
// TODO: Support pre-filled download info (from carrier apps); UX
|
||||
requireViewById<View>(R.id.lui_download).setOnClickListener {
|
||||
startActivity(Intent(this, DirectProfileDownloadActivity::class.java))
|
||||
startActivity(Intent(this, DownloadWizardActivity::class.java))
|
||||
finish()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -17,19 +17,16 @@ class PrivilegedEuiccManagementFragment: EuiccManagementFragment() {
|
|||
private var isMEP = false
|
||||
private var isRemovable = false
|
||||
|
||||
override suspend fun doRefresh() {
|
||||
super.doRefresh()
|
||||
withEuiccChannel { channel ->
|
||||
isMEP = channel.isMEP
|
||||
isRemovable = channel.port.card.isRemovable
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun onCreateFooterViews(
|
||||
parent: ViewGroup,
|
||||
profiles: List<LocalProfileInfo>
|
||||
): List<View> =
|
||||
super.onCreateFooterViews(parent, profiles).let { footers ->
|
||||
withEuiccChannel { channel ->
|
||||
isMEP = channel.isMEP
|
||||
isRemovable = channel.port.card.isRemovable
|
||||
}
|
||||
|
||||
if (isMEP) {
|
||||
val view = layoutInflater.inflate(R.layout.footer_mep, parent, false)
|
||||
view.requireViewById<Button>(R.id.footer_mep_slot_mapping).setOnClickListener {
|
||||
|
|
|
@ -16,10 +16,9 @@ class PrivilegedMainActivity : MainActivity() {
|
|||
menu.findItem(R.id.slot_mapping).isVisible = false
|
||||
}
|
||||
|
||||
if (tm.supportsDSDS) {
|
||||
val dsds = menu.findItem(R.id.dsds)
|
||||
dsds.isVisible = true
|
||||
dsds.isChecked = tm.dsdsEnabled
|
||||
menu.findItem(R.id.dsds).apply {
|
||||
isVisible = tm.supportsDSDS
|
||||
isChecked = tm.dsdsEnabled
|
||||
}
|
||||
|
||||
return true
|
||||
|
|
20
app/src/main/res/values-zh-rCN/strings.xml
Normal file
20
app/src/main/res/values-zh-rCN/strings.xml
Normal file
|
@ -0,0 +1,20 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="no_euicc">在此设备上找不到 eUICC 芯片。\n在某些设备上,您可能需要先在此应用的菜单中启用双卡支持。</string>
|
||||
<string name="dsds">双卡</string>
|
||||
<string name="toast_dsds_switched">双卡支持状态已切换。请等待基带重新启动。</string>
|
||||
<string name="footer_mep">此卡槽支持多个启用配置文件 (MEP)。要启用或禁用此功能,请使用\"卡槽映射\"工具。</string>
|
||||
<string name="slot_mapping">卡槽映射</string>
|
||||
<string name="slot_mapping_logical_slot">逻辑卡槽 %d:</string>
|
||||
<string name="slot_mapping_port">卡槽 %1$d 端口 %2$d</string>
|
||||
<string name="slot_mapping_help">您的手机有 %1$d 个逻辑 SIM 卡槽和 %2$d 个物理 SIM 卡槽。%3$s\n\n选择您希望每个逻辑卡槽对应的物理卡槽 和/或 \"端口\"。请注意,并非所有映射模式都受硬件支持。</string>
|
||||
<string name="slot_mapping_help_mep">\n\n物理卡槽 %1$d 支持多个启用的配置文件 (MEP)。要使用此功能,请将其 %2$d 个虚拟\"端口\"分配给上面显示的不同逻辑卡槽。\n\n启用 MEP 后,\"端口\"会在 OpenEUICC 中显示为共享 eSIM 配置文件的独立的 eSIM 卡槽。</string>
|
||||
<string name="slot_mapping_help_dsds">\n支持双卡模式,但已禁用。如果您的设备带有内置 eSIM 芯片,则默认情况下可能不会启用。更改上面的映射或启用双卡以访问您的 eSIM。</string>
|
||||
<string name="slot_mapping_completed">您的新卡槽映射已设置完毕。请等待基带刷新卡槽。</string>
|
||||
<string name="slot_mapping_failure">指定的映射可能无效或硬件不支持您指定的映射。</string>
|
||||
<string name="lui_title">通过下载 eSIM 连接到移动网络</string>
|
||||
<string name="lui_desc">您的设备支持 eSIM。要连接到移动网络,请下载运营商发布的 eSIM,或插入物理 SIM 卡。</string>
|
||||
<string name="lui_skip">跳过</string>
|
||||
<string name="lui_download">下载 eSIM</string>
|
||||
<string name="telephony_manager">TelephonyManager (特权)</string>
|
||||
</resources>
|
Loading…
Add table
Reference in a new issue